Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

wonder95's avatar

403 - Invalid Signature on Temporary Signed Route

I have created a way for users of my site to confirm attendance for a class using a temporary signed route. It works fine on my local and my default site (hosted on Forge), but on the live site, I get the lovely 403 - Invalid signature error.

The link generated in an email

$this->confirm_url = URL::temporarySignedRoute('application.class.confirm', now()->addWeeks(2), ['user' => $user->id, 'oclass' => $class_id]);

and the application.class.confirm route definition

Route::get('application/confirm/{user}/{oclass}', [ApplicationController::class, 'confirm'])
    ->name('application.class.confirm')
    ->middleware('signed');

I have looked in all my logs (nginx, laravel, etc) and can't find anything. I have also tried

protected $proxies = '*';

checking my nginx config settings,

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

and other things but I still get the error. I am well within the time frame of the expiration timestamp.

What else should I check?

0 likes
5 replies
LaryAI's avatar
Level 58

It seems like you've covered some of the common issues that could cause a 403 - Invalid signature error with Laravel's temporary signed routes. Here are a few more things you can check to troubleshoot the problem:

  1. Ensure App Key Consistency: Make sure that the APP_KEY in your .env file is the same on your local, Forge, and live environments. The signed URL is generated using this key, so if it's different, the signature will be invalid.

  2. Correct Timezone Settings: Ensure that the server's timezone is correctly set and matches the timezone in your config/app.php. A mismatch can cause the signature to be invalid due to time differences.

  3. HTTPS vs. HTTP: If your local and Forge environments are running on HTTP and your live site is on HTTPS (or vice versa), this could cause signature mismatches. Make sure that the URLs are being generated with the correct scheme.

  4. URL Encoding: Ensure that the URL is not being encoded or decoded in a way that would alter the signature when it's sent in an email. For example, certain email clients might modify the URL, which would invalidate the signature.

  5. Query String Manipulation: If there's any middleware or server configuration that modifies the query string, it could affect the signature. Make sure nothing is altering the query string after the signed URL is generated.

  6. Load Balancers / Proxies: If you're using a load balancer or proxy, ensure that the TrustProxies middleware is correctly configured to trust the headers set by your proxy.

  7. Cache Configuration: If you're caching your routes, make sure to clear your route cache after any changes to your routes or environment settings with php artisan route:cache.

  8. Middleware Order: Ensure that no middleware is running before the signed middleware that could potentially alter the request.

If none of these suggestions resolve the issue, you might want to generate a signed URL on the live server and compare it to one generated on your local or Forge server to see if there's any noticeable difference. This could help pinpoint where the issue is occurring.

Here's a checklist you can use to debug the issue:

// Check APP_KEY consistency across environments
echo env('APP_KEY');

// Check timezone settings
echo config('app.timezone');

// Check URL scheme (HTTP vs HTTPS)
echo url()->current();

// Check for URL encoding issues
// Make sure the URL looks the same in the email as it does when it's generated

// Check for query string manipulation
// Look for any middleware or server config that might alter the query string

// Check TrustProxies middleware configuration
// Especially if you're behind a load balancer or proxy

// Clear route cache after changes
php artisan route:cache

// Check middleware order
// Ensure 'signed' middleware is not being affected by other middleware

Remember to remove or comment out any debug code like echo statements before pushing to your live environment.

wonder95's avatar
wonder95
OP
Best Answer
Level 5

In order to avoid being this guy, my problem was URL misconfiguration. In particular, my APP_URL value in prod was using http instead of https.

vincentc's avatar

Something else is going on with this error that I haven't figured out yet. I am using Breeze and Sail.

Using Caddy (https://owenconti.com/posts/local-laravel-development-with-https#content-laravel-sail), I was at least able to duplicate the issue locally when running https (cannot duplicate without https).

However, I am still not sure of what the root cause is. Another odd thing is that I ran the tests both inside and outside of Sail, and I would have expected the Tests\Feature\Auth\EmailVerificationTest test to fail outside of Sail, but it still passed:
PASS Tests\Feature\Auth\EmailVerificationTest.
✓ email verification screen can be rendered
✓ email can be verified
✓ email is not verified with invalid hash

I'm still investigating the issue.

vincentc's avatar

Update:

The issue in my case was more complex. In many cases, we have our applications behind a load balancer, and the load balancer is terminating the https, and sending the request to the application as http. Even with URL::forceScheme('https'); set, there is some core code in Laravel that explicitly looks at the protocol of the incoming request to build URLs. And this is so in the case of tracing the code for the email verification. So the mismatch of https and http meant that Laravel was generating an email verification hash that was always going to fail the internal validation of the hash. The solution to this problem is to use Trusted Proxies (https://laravel.com/docs/11.x/requests#configuring-trusted-proxies), which will tell Laravel that it can look at the x-forwarded headers, where it will see https in the x-forwarded-proto, and use that to correctly build the verification hash. Setting the trusted proxies worked for me.

1 like
danwah's avatar

@vincentc What a legend, thank you for posting this! I had this problem too - service behind a load balancer with SSL termination, signed urls were presenting as http instead of https. Adding protected $proxies = '*'; did the trick, although adding the load balancer IP addresses didn't work for whatever reason.

Thanks again!

1 like

Please or to participate in this conversation.