Laravel Signed Routes: Secure Link Patterns | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Laravel Signed Routes and Temporary URLs: Secure Link Patterns Beyond the Basics

 Laravel Signed Routes and Temporary URLs: Secure Link Patterns Beyond the Basics
=================================================================================

 Signed routes are more than a password-reset trick. Learn how to build expressive, tamper-proof URL workflows with custom guards, expiry strategies, and Filament-friendly action links.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 17 Jun 2026 · Updated 17 Jun 2026 · 4 min read

ShareCopy linkCopied

 ![Laravel Signed Routes and Temporary URLs: Secure Link Patterns Beyond the Basics](https://cdn.msaied.com/222/1bcb5bdf8f9b2490d0898cfa28479582.png) 

  On this page +1. [Why Signed Routes Deserve More Attention](#why-signed-routes-deserve-more-attention)
2. [How Signing Actually Works](#how-signing-actually-works)
3. [Per-Tenant Signing Keys](#per-tenant-signing-keys)
4. [One-Time Links via Nonce Invalidation](#one-time-links-via-nonce-invalidation)
5. [Filament Table Action Integration](#filament-table-action-integration)
6. [Protecting Signed Routes from Parameter Tampering](#protecting-signed-routes-from-parameter-tampering)
7. [Takeaways](#takeaways)

 Why Signed Routes Deserve More Attention
----------------------------------------

Most Laravel developers reach for signed routes exactly once — the built-in email verification flow — and then forget they exist. That's a missed opportunity. Signed URLs are a lightweight, stateless alternative to tokens stored in a database, and they compose cleanly with Laravel's existing middleware stack.

This article focuses on the practical patterns that go beyond the defaults: custom signing keys per tenant, expiry strategies, invalidation via nonce, and wiring signed links into Filament table actions.

---

How Signing Actually Works
--------------------------

When you call `URL::signedRoute()` or `URL::temporarySignedRoute()`, Laravel appends a `signature` query parameter computed with HMAC-SHA256 over the full URL (including any expiry timestamp) using `APP_KEY` as the secret.

```php
$link = URL::temporarySignedRoute(
    'invoice.download',
    now()->addMinutes(30),
    ['invoice' => $invoice->id]
);

```

The middleware `signed` (alias for `ValidateSignature`) rejects any request where the signature doesn't match or the `expires` timestamp has passed — no database round-trip required.

---

Per-Tenant Signing Keys
-----------------------

In a multi-tenant app you may want tenant A's signed links to be invalid on tenant B's subdomain. Laravel's `URL::signedRoute()` uses `APP_KEY` globally, but you can swap the key contextually by resolving a custom `UrlGenerator` or, more practically, by verifying the signature manually inside a custom middleware.

```php
// app/Http/Middleware/ValidateTenantSignature.php
public function handle(Request $request, Closure $next): Response
{
    $tenant = app('currentTenant');
    $secret = $tenant->signing_secret; // stored per tenant

    $expected = hash_hmac(
        'sha256',
        $request->fullUrlWithoutQuery() . '?'
            . Arr::query(Arr::except($request->query(), 'signature')),
        $secret
    );

    if (! hash_equals($expected, (string) $request->query('signature'))) {
        abort(403, 'Invalid signature.');
    }

    if ($request->has('expires') && now()->timestamp > (int) $request->query('expires')) {
        abort(403, 'Link expired.');
    }

    return $next($request);
}

```

Generate the link using the same HMAC logic in a dedicated action class so the signing logic lives in one place.

---

One-Time Links via Nonce Invalidation
-------------------------------------

Signed URLs are stateless, so they can be replayed until they expire. For truly one-time links (e.g., a magic login), combine a short expiry with a nonce stored in cache:

```php
final class GenerateMagicLink
{
    public function handle(User $user): string
    {
        $nonce = Str::uuid()->toString();

        Cache::put("magic:{$nonce}", $user->id, now()->addMinutes(10));

        return URL::temporarySignedRoute(
            'auth.magic',
            now()->addMinutes(10),
            ['nonce' => $nonce]
        );
    }
}

```

```php
// In the controller
public function __invoke(Request $request): RedirectResponse
{
    $request->validateSignature(); // throws if invalid/expired

    $userId = Cache::pull("magic:{$request->nonce}"); // pull = get + delete

    abort_if($userId === null, 403, 'Link already used.');

    Auth::loginUsingId($userId);

    return redirect()->intended('/dashboard');
}

```

`Cache::pull()` atomically retrieves and deletes the nonce, preventing replay without a separate "used" flag in the database.

---

Filament Table Action Integration
---------------------------------

Filament's `Action::url()` accepts a closure, making signed links trivial to embed:

```php
Tables\Actions\Action::make('download')
    ->label('Download Invoice')
    ->icon('heroicon-o-arrow-down-tray')
    ->url(fn (Invoice $record): string =>
        URL::temporarySignedRoute(
            'invoice.download',
            now()->addHour(),
            ['invoice' => $record->id]
        )
    )
    ->openUrlInNewTab(),

```

Because the URL is generated server-side at render time, the signature is always fresh and scoped to the authenticated user's session context.

---

Protecting Signed Routes from Parameter Tampering
-------------------------------------------------

A subtle gotcha: if you add query parameters to a signed URL after generation (e.g., a UTM tag), the signature breaks. Teach consumers to append extra parameters *before* signing, or strip known analytics params in middleware before validation:

```php
// In a custom ValidateSignature override
protected $except = ['utm_source', 'utm_medium', 'utm_campaign'];

```

Laravel's built-in `ValidateSignature` middleware accepts an `$except` property for exactly this purpose.

---

Takeaways
---------

- Signed routes are stateless and require no token table — ideal for short-lived, low-stakes workflows.
- Per-tenant signing keys need a custom middleware; the built-in one always uses `APP_KEY`.
- Combine `Cache::pull()` with a short expiry for true one-time links without a database migration.
- Filament's `Action::url()` closure makes signed link generation a one-liner in table definitions.
- Strip analytics query params via `$except` before signature validation to avoid false 403s.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [security](https://www.msaied.com/public/articles?search=security)
- [routing](https://www.msaied.com/public/articles?search=routing)
- [filament](https://www.msaied.com/public/articles?search=filament)

 Frequently asked questions 
---------------------------

  Can a signed URL be invalidated before it expires?Not natively — signed URLs are stateless. The standard pattern is to pair them with a cache-based nonce using Cache::pull(), which deletes the nonce on first use and effectively invalidates the link without touching the database.

   Does adding UTM parameters to a signed URL break the signature?Yes. Any change to the URL after signing invalidates the HMAC. Either include UTM params before signing, or list them in the $except array on the ValidateSignature middleware so they are ignored during verification.

   Is it safe to embed signed URLs in emails?Yes, with a short expiry. Use temporarySignedRoute() with an expiry appropriate to the action (e.g., 24 hours for email verification, 10 minutes for magic login). For sensitive actions, add the nonce pattern to prevent replay if the email is forwarded.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleThe artisan dev Command and What's New in Laravel 13.16.0](https://www.msaied.com/public/articles/the-artisan-dev-command-and-whats-new-in-laravel-13160) [Next articleLaravel Contextual HTTP Clients: Per-Service Config, Retries, and Middleware Stacks](https://www.msaied.com/public/articles/laravel-contextual-http-clients-per-service-config-retries-and-middleware-stacks)  

   On this page
-------------

1. [Why Signed Routes Deserve More Attention](#why-signed-routes-deserve-more-attention)
2. [How Signing Actually Works](#how-signing-actually-works)
3. [Per-Tenant Signing Keys](#per-tenant-signing-keys)
4. [One-Time Links via Nonce Invalidation](#one-time-links-via-nonce-invalidation)
5. [Filament Table Action Integration](#filament-table-action-integration)
6. [Protecting Signed Routes from Parameter Tampering](#protecting-signed-routes-from-parameter-tampering)
7. [Takeaways](#takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
