Laravel 5.7 email verification throws 403 - laravel

I implemented email verification in a Laravel 5.7 project I'm working on. I get the email, but whenever I click on the confirm button or even the url provided in the email, I get a 403 forbidden error.
I have searched for several solutions, but haven't been able to find one to this problem. The only reasonable pointers to this error is this github issue https://github.com/laravel/framework/issues/25716 which has been merged and closed by Taylor Otwell by still this problem persists.
Here's the email I get:
Here's the error it throws when I click on the button or the actionUrl at the email footer: and here's the url shown when the 403 page is displayed https://www.mywebsite.com/email/verify/1?expires=1540140119&signature=fd7dc72b05da6f387b2f52a27bceee533b2256436f211930c1319c7a544067da
Please help me. Thank you
Edits: This problem occurs only in production app. On local, this email verification works but throws 403 on production(live) server. My email service is mailgun, and I can access every other email contents relating to the app except completing email verification.
I need help please. Thanks in anticipation

One of the reasons that was in my case can be that you are already logged in with a normal verified user, and you have clicked on the verification email link. In that case it will shoot 403 . Which is not normal in my opinion, but whatever.

For me because manually create verification route. which in laravel 6.x or 7.x The route path for verifying emails has changed. from /email/verify/{id} to /email/verify/{id}/{hash} This probably only happens because I use the rules manually, and not Auth::routes(['verify' => true])
for more information laravel upgrade guide upgrade#email-verification-route-change

This typically occurs if your application is running behind some proxies and probably doesn't handle SSL termination itself.
The solution is to add
protected $proxies = '*';
to the TrustProxies middleware.
Reference: https://laracasts.com/discuss/channels/laravel/hitting-403-page-when-clicking-verify-link-in-email-using-new-laravel-verification-57?page=1

Turns out, this often happens when you have your laravel app running behind a proxy (apache, nginx etc.) We therefore end up replacing laravel's default 'signed' middleware with our own middleware that checks for https:// links. This StackOverFlow answer here was able to fix this problem for me:
Signed route for email verification does not pass signature validation

To use Laravel email verification you must first add the proper routes.
If you take a look at Illuminate/Routing/Router.php you'll see that by default the verify route is disabled.
if($options['verify'] ?? false)
{
$this->emailVerification();
}
To enable your verification routes add the following to your web.php
Auth::routes(['verify'=>true]);
Then run
php artisan route:list
to make sure that it's working.

Check the verify method inside the VerifiesEmails trait,
there they have:
if (! hash_equals((string) $request->route('hash'), sha1($request->user()->getEmailForVerification()))) {
throw new AuthorizationException;
}
I have dumped this variable $request->route('hash') and it was null, so I overrided it in the VerificationController:
/**
* Mark the authenticated user's email address as verified.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
* #throws \Illuminate\Auth\Access\AuthorizationException
*/
public function verify(Request $request)
{
if (! hash_equals((string) $request->route('id'), (string) $request->user()->getKey())) {
throw new AuthorizationException;
}
if (! hash_equals((string) $request->query('hash'), sha1($request->user()->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($request->user()->hasVerifiedEmail()) {
return redirect($this->redirectPath());
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect($this->redirectPath())->with('verified', true);
}
And now it works!

The problem for me was my APP_URL had a protocol of http and when I clicked on the verification link NGINX automatically redirected the url from http to https that's why the signature validation failed. I updated the APP_URL to have a protocol of https and that resolved my problem.

My personal experience with this problem was that I set MAIL_DRIVER to log in the .env file, and Laravel escaped special characters (such as &) when it stored the activation link in the log.
So NEVER use the log for MAIL_DRIVER when you have verification email.
(my Laravel version was 5.8).

Related

Laravel Email Verification 5.8 with sqlsrv

I implemented the email verification that Laravel offers out of the box but currently have an issue when one click on the verification link sent on email. It brings the error: Data Missing
This error is throw by Carbon at the point when the column email_verified_at is being updated, and this was only on the SQLSRV implementation. I switched to a test MySQL database and this worked, although I need it to work with SQLSRV. I have not checked to see if any other database implementation encounters this problem.
Add it into app\Http\Controller\Auth\VerificationController.php
protected function getDateFormat()
{
return 'Y-m-d H:i:s';
}

How to fix Laravel request/routes/urls - it thinks url is http when it is really https

My server uses SSL and thus all my routes/urls use https. I recently discovered a bug in Laravel 5.7 which was exposed when trying to use Email Verification, which does not work on a server with https. I wont go into the specifics of that problem as I have another question for that. I want to keep this simple.
I have the following settings in my .env file:
APP_USE_HTTPS=true
APP_URL=https://www.example.com
APP_ENV=production
And I have the following in the boot() method of the AppServiceProvider
if (env('APP_USE_HTTPS')) {
Log::info('AppServiceProvider: forcing URLs to use https');
URL::forceScheme('https');
}
And it may be overkill but to try to resolve the issue I also put the following code at the top of my web.php routes file"
if (env('APP_USE_HTTPS')) {
Log::info('Routes: forcing URLs to use https');
URL::forceScheme('https');
}
Route::get('/', 'PublicController#getHome');
Route::get('home', 'PublicController#getHome');
Then in my PublicController.getHome() method I put this code:
public function getHome()
{
$currentPath= Request::fullUrl();
Log::info($currentPath);
return view('public.home');
}
Now I go to my browser and enter this in the address bar:
https://www.example.com
And I get this in my log file:
AppServiceProvider: forcing URLs to use https
Routes: forcing URLs to use https
http://www.example.com
So as you can see from the last log message the fact that laravel always uses http instead of https is beginning to create issues. Starting with signed routes. I am trying to use the built-in Email Verification but the signature is being generated using https route and the email sent to user does have https in the url for going back to the same server. However the validation for the route is using http (even though https was used) so it generates a different signature and thus all verifications links fail with a 403 error.
Is there anything I am missing? I can't seem to find code that shows me how Laravel knows to use https or http or is it just hard coded for http?
Thanks for any help you can give me.
*** Update to show problem with Shaielndra Gupta answer ****
After implementing the middleware below is the code I used but as you will see the core problem exists in ALL methods dealing with url. So for example:
$request->secure()
returns false even when https was used. Then by calling:
redirect()->secure($request->getRequestUri());
does no good because that will cause the route to loop back into this method again which still returns false for secure(), basically creating an infinite loop (or infinite too many redirects)
class ForceHttpsProtocol {
public function handle($request, Closure $next) {
Log::info('request uri: '.$request->fullUrl());
Log::info('secure: '.($request->secure() ? 'yes' : 'no'));
if (!$request->secure() && env('APP_USE_HTTPS')) {
return redirect()->secure($request->getRequestUri());
}
return $next($request);
}
}
The log from the above code will produce the following when you make 1 attempt to go to any page even when using https://www.example.com
request uri: http://www.example.com
secure: no
request uri: http://www.example.com
secure: no
request uri: http://www.example.com
secure: no
request uri: http://www.example.com
secure: no
< over and over till page times out >
After much research I finally discovered what the issue is.
My live server is installed on an Amazon EC2 server which is behind a Load Balancer.
The load balancer is receiving the (https) request and handling all the SSL requirements and then forwarding the request to my website as http.
To fix this issue I had to install the fideloper/TrustedProxy package. This package allows my site to trust the proxy and get the valid headers for the request so it now knows the request was actually sent using https.
Laravel wrote an article which describes my condition exactly.
https://laravel-news.com/trusted-proxy
This is the package I installed:
https://github.com/fideloper/TrustedProxy
change in your config/session.php
'http_only' => true,
change it to
'http_only' => false,
or make a middlewere HttpsProtocol.php
namespace MyApp\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\App;
class HttpsProtocol {
public function handle($request, Closure $next)
{
if (!$request->secure() && App::environment() === 'production')
{
return redirect()->secure($request->getRequestUri());
}
return $next($request);
}
}
Then, apply this middleware to every request adding setting the rule at Kernel.php file in protected $routeMiddleware array,
'https'=>App\Http\Middleware\HttpsProtocol::class
change This
APP_USE_HTTPS=true
APP_URL=https://www.example.com
to this
APP_URL=http://www.example.com
APP_USE_HTTPS=false
Because Laravel uses APP_URL to generate urls.

Check Laravel 5.7 login from external script

I have a Laravel app and I need to check if a user is logged and who from a external script. I'm using the following lines to load Laravel and try to check it.
require_once __DIR__.'/../../../vendor/autoload.php';
$app = require_once __DIR__.'/../../../bootstrap/app.php';
$app->make('Illuminate\Contracts\Http\Kernel')
->handle(Illuminate\Http\Request::capture());
/*if (Cookie::get(config('session.cookie')) != "") {
$id = Cookie::get(config('session.cookie'));
Session::driver()->setId($pericod);
Session::driver()->start();
}*/
$isAuthorized = Auth::check();
if(!$isAuthorized){
echo "NO AUTORIZADO";
exit();
}
With this lines I can access any Laravel function and I can check the login if I made GET request to the external scripts, but when the request is POST it always fails. I'm unable to check the login and I see that the session changes because can't get the existing session.
I have made many tests and I think that somethings of Laravel are not working fine, like routes or middlewares. I can made it work if I disable all encryption of the cookies and the session, but I want to use this security functions.
I'm using updated Laravel 5.7 and I had this code working in Laravel 5.4
Thank you for your help.
I discovered the problem,
The trick is that the route is external to laravel so laravel's route resolver identifies the current route as /.
It was working on GET requests because in my routes file I have the / route only as get. If I set the / route as any, everything works.
I wasn't seeing the problem because I was not terminating Laravel's execution. If I change the logged user verification to this, it shows the error:
$isAuthorized = Auth::check();
if(!$isAuthorized){
echo "NO AUTORIZADO";
$response->send();
$kernel->terminate($request, $response);
exit();
}
This two lines ends laravel execution and returns the error "405 Method Not Allowed".
$response->send();
$kernel->terminate($request, $response);
Thank you for your help.

Bitbucket - Webhook keep returning error 500

Would like to check, I am fairly new to Bitbucket's new introduced webhook where previously i was using services where Bitbucket will execute a link to my site thus triggering a deployment script.
So since the old service is going to be depreciated soon, we all migrated to webhook instead. With the same implementation, I keep getting an error 500 upon commit/push/merge and there is no way for us to see the details for the error given. At first I thought it was my server giving problem but when i call the link manually via browsers and everything was fine. The deployment script can be executed successfully so then why bitbucket's webhook keeps telling me error 500?
Subsequently I find the guide given by Bitbucket was not helpful. There is no specified call method to the url stated so is the webhook initiates a GET or POST request? previously using services initiates a POST request. Then, are there any necessary payloads i need to include into the webhook URL? None is stated. Then, if there is an error at least let me see the error so I can fix it instead of telling me error 500.
I hope someone here can help me with this issue. Below are some specification of the site.
Server : Ubuntu LEMP 14.04 x64 Laravel framework 5.0
Webhook Url: bot.example.com/bitbucket/deploy/{Site API}
Method : GET
And when the abode link is call, it reaches a controller that does
public function attemptDeploy($site_api)
{
$script = 'nohup setsid php ~/scripts/deploy.php ' . $site_api. ' > /dev/null 2>&1 &';
exec($script);
return response('Deploy running.', 200);
}
Note that when i call this link manually either form browser or console everything works perfectly except from bitbucket's webhook. How can i solve this issue?
I was in the same situation. Bitbucket trims the body of the response and I couldn't see the error given by my server.
I've looked into the logs storage/logs/laravel.log and saw TokenMismatchException. Webhooks being API calls they don't store cookies or sessions so CSRF from Laravel breaks.
You need to add an exception from CSRF for the bitbucket deploy route. You can add this exception in app/Http/Middleware/VerifyCsrfToken.php. For example if your link is www.your_site.com/bit_deploy you will have:
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'bit_deploy'
];
}
Hope that this helps you ... as I've lost 3 hours on this.
PS: at the time of writing this answer, bitbucket webhooks performs POST calls (not GET)

Paypal's IPN Simulator using Laravel 5

I'm about to go mental with this problem, I'm implementing an IPN system in my app and started doing tests now using Paypal's IPN Simulator.
When I try to send an IPN simulation, it just gives the following error:
We're sorry, but there's an HTTP error. Please try again.
First thought - Paypal's service was down - Tested wrong since if I create a blank page and send an IPN message to http://myDNS.com/blankpage.php it is able to send it.
Second thought - Problem with routes - which I think it's not the problem either:
Here's my IPN Listener at the PurchaseController.php:
public function completed()
{
//FAHIM's Paypal IPN Listener
$ipn = new PaypalIPNListener();
$ipn->use_sandbox = true;
$verified = $ipn->processIpn();
$report = $ipn->getTextReport();
Log::info("-----new payment-----");
Log::info($report);
if ($verified) {
if($_POST['address_status'] == 'confirmed'){
//sucess
}
}
}
In routes.php :
Route::post('purchase/completed/', array('as' => 'purchase.completed', 'uses' => 'PurchaseController#completed'));
Is there any known problems associated with IPN Simulator and Laravel?
Thank you in advance.
Looks like I found the answer!
The problem was that a tokenMismatchException was being thrown whenever Paypal tried to send the POST information.
For people with the same problem, here's the solution:
Add an exception into the VerifyCsrfToken.php Middleware, so that the exception URI won't need the CsrfToken verification:
In my case, it looks something like this:
protected $except = [
'purchase/completed'
];
I'm working with Laravel 5, so please keep in mind that it might be slightly different in lower versions.

Resources