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

ahoi's avatar

Fortify: Verify registration with signed:relative

Hi there,

I would like to solve this problem: I got a application that uses a standalone vue frontend, which consumes the api. That works fine. Fortify is just of great help here.

But there is one tiny little problem: The vue app uses a nitro proxy to proxy api calls:

http://example.org/backend/api -> http://api.example.com/api

Now the confirmation mails already include a link to the frontend, containing user id, hash, expires and signature. Those information are being used to call:

http://example.org/backend/email/verify/{user_id}/{hash}?expires={expires}&signature={signature}

Now there is a problem: The validation of the signature does not work, because it is hard-coded in the routes.php of fortify:

<?php

// Email Verification...
if (Features::enabled(Features::emailVerification())) {

    Route::get(RoutePath::for('verification.verify', '/email/verify/{id}/{hash}'), [VerifyEmailController::class, '__invoke'])
        ->middleware([config('fortify.auth_middleware', 'auth').':'.config('fortify.guard'), 'signed', 'throttle:'.$verificationLimiter])
        ->name('verification.verify');

}

And there it is: 'signed'. I'd need signed:relative instead, but there is no way to configure that.

What can I do to overwrite this route?

0 likes
1 reply
LaryAI's avatar

You are correct: Laravel Fortify registers its own routes internally, and by default, the email verification route uses the signed middleware, which expects an absolute URL signature. When you proxy or change the host, the signature validation fails.

To solve this, you need to override the default Fortify email verification route and register your own with the signed:relative middleware.

Here’s how you can do it:

1. Disable Fortify's Default Email Verification Routes

In your FortifyServiceProvider, inside the boot method, you can disable Fortify's default routes by setting the FORTIFY_ROUTES environment variable to false before Fortify registers its routes:

public function boot()
{
    // Disable Fortify's default routes
    Fortify::ignoreRoutes();

    // Register your custom routes here...
}

2. Register Your Own Email Verification Route

Now, in your routes/web.php or a custom routes file, register the verification route yourself, using signed:relative:

use Laravel\Fortify\Features;
use Laravel\Fortify\Http\Controllers\VerifyEmailController;

if (Features::enabled(Features::emailVerification())) {
    Route::get('/email/verify/{id}/{hash}', [VerifyEmailController::class, '__invoke'])
        ->middleware([
            config('fortify.auth_middleware', 'auth').':'.config('fortify.guard'),
            'signed:relative',
            'throttle:6,1',
        ])
        ->name('verification.verify');
}
  • Adjust the throttle middleware as needed (the default is 6,1 for 6 attempts per minute).

3. Make Sure the Frontend Generates Relative Signed URLs

When generating the verification URL, ensure you use Laravel’s URL::temporarySignedRoute() with a relative path. If you’re generating the link in the backend, you can do:

$url = URL::temporarySignedRoute(
    'verification.verify',
    now()->addMinutes(60),
    ['id' => $user->getKey(), 'hash' => sha1($user->getEmailForVerification())]
);

This will generate a URL that works with the signed:relative middleware.

4. Clear Route Cache

If you’re using route caching, don’t forget to clear it:

php artisan route:clear

Summary:

  • Call Fortify::ignoreRoutes() in your service provider.
  • Register your own /email/verify/{id}/{hash} route with the signed:relative middleware.
  • Ensure your frontend or backend generates relative signed URLs.

This will allow your email verification to work correctly even when proxied or accessed via a different host.

Let me know if you need a full code example or further clarification!

Please or to participate in this conversation.