Laravel 403 Invalid Signature only on Nginx Web Server

Viewed 3660

Problem

I am creating a signed URL through laravels 'temporarySignedRoute' function copied and pasted from laravels signed routes documentation, on my web server the url comes out to a 403 invalid signature. The signed URL's work on my local machine but upon uploading it to my test server it gives the error shown below.

enter image description here

What I've tried

I have been searching for a solution for awhile now and have tried many that have been posted on stack overflow but nothing seems to solve my problem.

I have tried all of the following:

  • Forcing all routes to https through the AppServiceProvider, this forced all of the url's to be https but gave the same result.
  • I tried changing Nginx's configuration as described here.

  • I also tried adding the TrustProxies middleware with the proxies configured to '*' or all, like described in laravel's documentation on Trust Proxies, same result.

  • Stopped the web server from forcing the domain to https to test if it was just https causing it, same result.

-- UPDATE --

I went into vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php while trying to search around for any clues in the project. I decided to datadump (dd) out the tokens it was comparing on my local and hosted projects and this was the result. Code shown below as well.

public function hasValidSignature(Request $request, $absolute = true)
{
    $url = $absolute ? $request->url() : '/'.$request->path();

    $original = rtrim($url.'?'.Arr::query(
        Arr::except($request->query(), 'signature')
    ), '?');

    $expires = $request->query('expires');

    $signature = hash_hmac('sha256', $original, call_user_func($this->keyResolver));
    dd($signature . " :: " . $request->query('signature', ''));
    return  hash_equals($signature, (string) $request->query('signature', '')) &&
           ! ($expires && Carbon::now()->getTimestamp() > $expires);
}

Local Project

local project

Hosted Project

remote machine

So the Token is not being passed through on my web server but is on my local. Also another thing I noticed is that the web servers token is always the same and never changes no matter what.

My current Nginx configuration can be viewed here.

My current virtual hosts configuration is here.

3 Answers

I think this might be the issue. Try to change your location block in the config file to

try_files $uri $uri/ /index.php?$query_string;

I think you have missed the ? between index and query string.

Update this and make sure to reload your Nginx server. sudo service nginx reload

Let me know if this works.

The post from Prince Kumar Barnwal fixed my issue back then, I recently rediscovered another version of this issue while attempting to use docker in the same setup and I thought I would post it here in case anyone has the same issue.

We are using a Proxy Server and certifying our docker laravel projects on that, all projects have the TrustProxies setup, then we forceHTTPS in the AppServiceProvider.php which makes AJAX calls work. This causes issues as the project itself is not encrypted but all urls it generates thinks it is since we are forcing it. When the proxy passes the URL in the request it is http, when the project expects HTTPS.

I found a solution to this, its not the most clean and theirs probably a better fix out there but I made a middleware that goes on all of the projects signed routes, basically it just changes the url in the request to be secure if the project is in production. This is not insecure as the proxy handles the security and this is ONLY on the signed routes.

Below is the middleware that goes ONLY on the signed routes.

<?php

namespace App\Http\Middleware;

use Closure;

class SecureSignedURL
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if(config('app.env') === 'production' || config('app.env') === 'staging') {
            $request->server->set('HTTPS', 'ON');
        }
        return $next($request);
    }
}

My web.php with the middleware, its defined as secure_signed_url in my Kernel.php

Route::group(['middleware' => ['secure_signed_Url', 'signed']], function () {
        route::get('/exampleurl1', 'TestController@route_exampleurl1_signed')->name('exampleurl1.signed');
        route::get('/exampleurl2', 'Template\TemplateController@route_exampleurl2_signed')->name('exampleurl2.signed');
});

Again I don't know if this is the best fix, this is a very obscure bug in Laravel. If you have any suggestions for a better way of fixing this I'm open to suggestions.

My Old Github Issue that may help: https://github.com/laravel/framework/pull/31434

This is not a Nginx error. That's how laravel handles invalid signed urls if you check the Illuminate\Routing\Middleware\ValidateSignature it throws an Illuminate\Routing\Exceptions\InvalidSignatureException which aborts the request with a 403 status. What I did, Created a custom signed middleware which extends the actual ValidateSignature

<?php

namespace App\Http\Middleware;

use App\Exceptions\InvalidEmailVerificationException;
use Closure;
use Illuminate\Routing\Middleware\ValidateSignature as MiddleWare;

class ValidateSignature extends Middleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->hasValidSignature()) {
            return $next($request);
        }

        throw new InvalidEmailVerificationException;
    }
}

which throws a custom App\Exceptions\InvalidEmailVerificationException

<?php

namespace App\Exceptions;

use Symfony\Component\HttpKernel\Exception\HttpException;;

class InvalidEmailVerificationException extends HttpException
{
    /**
     * Create a new exception instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct(403, 'Your email verification token seems to be invalid.');
    }
}

then change the signed middleware in your App\Http\Kernel.php $routeMiddleware to 'signed' => \App\Http\Middleware\ValidateSignature::class,

in your 403.blade.php view, you can display the message as

@if ($exception instanceof \App\Exceptions\InvalidEmailVerificationException)
                            <h1 class="text-ginormous">Snap!</h1>
                            <h4 class="quick-error-description">Verification token expired.</h4>
                            @if (session('resent'))
                                <div class="alert alert-success" role="alert">
                                    {{ __('A fresh verification link has been sent to your email address.') }}
                                </div>
                            @endif
                            <p class="error-description">{{ $exception->getMessage() }}</p>

                            <a href="{{ route('verification.resend') }}" class="button-solid button-error mb-3" data-no-pjax>Email me a new link</a>
@endif
Related