Signing URLs that do not belong to your Laravel app
Create and validate signed URLs with a limited lifetime
At a glance
- What is it?
- spatie/laravel-url-signer adds an expiry and a signature to any URL, using a secret that is separate from the Laravel app key. It suits teams that hand out expiring links across hosts, and it leaves revocation, auditing and host allow-lists undocumented.
- Who is it for?
- Adopt spatie/laravel-url-signer when links must be signed across hosts or must outlive an app key rotation, and skip it when you need single-use links, an audit trail of validation attempts, or a signed URL that expires at second granularity inside a Laravel app that already handles signed routes.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Signing URLs that do not belong to your application
Laravel ships its own mechanism for signed routes, but this package exists for a different problem: putting a signature on any URL, including one that lives on another host. The README frames this as three differences from native route signing, and the first is the useful one. A signed link can travel between applications, so a web app can hand a customer a link to a document, a report, or an API endpoint hosted somewhere else entirely. The other two differences are about control rather than reach. The secret that produces the signature is not the application key, and the URL being signed does not have to be a route your application registered. That combination makes the package a fit for teams that need expiring, tamper-evident links across service boundaries, and a poor fit for anything that needs finer control over who consumed a link and when.
A signature key that lives outside APP_KEY
The signing secret sits at the centre of the design. Installation requires an environment variable called URL_SIGNER_SIGNATURE_KEY, and the published configuration file reads it with env('URL_SIGNER_SIGNATURE_KEY'). Because that value is independent of the Laravel application key, rotating the app key does not invalidate links you have already handed out, and a second environment can hold the same secret to validate links it never generated. A signed URL carries two query parameters, expires and signature, and even those names are configurable through the parameters array in the config file. The default lifetime is expressed in seconds and set to 60 * 60 * 24, so a signed link lives for one day unless you change the config or pass an explicit expiry.
Installing the package and generating the signature key
The README gives composer as the install path. Pull the package in first, then generate a secret. The generator accepts three options that are worth reading before you run it, because the default writes into your environment files and the show option displays the key instead of modifying files.
composer require spatie/laravel-url-signerphp artisan generate:url-signer-signature-key
{--s|show : Display the key instead of modifying files.}
{--always-no : Skip generating key if it already exists.}
{--f|force : Skip confirmation when overwriting an existing key.}The config file is optional and is published under its own tag.
php artisan vendor:publish --tag="url-signer-config"Once published, the file holds the key reference, the default lifetime, and the query parameter names.
return [
/*
* This string is used the to generate a signature. You should
* keep this value secret.
*/
'signature_key' => env('URL_SIGNER_SIGNATURE_KEY'),
/*
* The default expiration time of a URL in seconds.
*/
'default_expiration_time_in_seconds' => 60 * 60 * 24,
/*
* These strings are used a parameter names in a signed url.
*/
'parameters' => [
'expires' => 'expires',
'signature' => 'signature',
],
];Run the test suite the same way the README documents it.
composer testSigning with a chosen lifetime, then validating the result
Signing is a single facade call. Without a second argument the package applies the configured default, one day.
use Spatie\UrlSigner\Laravel\Facades\UrlSigner;
UrlSigner::sign('https://myapp.com/protected-route');The second argument takes either a date or a number of seconds, and the comment in the README states plainly that the generated URL is valid for 5 minutes in the first form.
use Spatie\UrlSigner\Laravel\Facades\UrlSigner;
// the generated URL will be valid for 5 minutes.
UrlSigner::sign('https://myapp.com/protected-route', now()->addMinutes(5));
// alternatively you could also pass the amount of seconds
UrlSigner::sign('https://myapp.com/protected-route', 60 * 5);The signed result has the shape of the original URL with an expires and a signature query parameter appended, and validation is a boolean returned from the same facade.
use Spatie\UrlSigner\Laravel\Facades\UrlSigner;
// returns `true` if the signed URL is valid, `false` if not
UrlSigner::validate('https://app.com/protected-route?expires=xxxxxx&signature=xxxxxx');The signed-url middleware answers a bad signature with a 403
Validating inside a controller means remembering to call validate() on every entry point. The package ships a middleware for that job, and it has to be registered under a name before it can be applied to a route. The README names the middleware class explicitly.
// in app/Http/Kernel.php
protected $routeMiddleware = [
// ...
'signed-url' => \Spatie\UrlSigner\Laravel\Middleware\ValidateSignature::class,
];Once registered, the alias attaches to any route you want to guard.
Route::get('protected-route', fn () => 'Hello secret world!')
->middleware('signed-url');The documented behaviour is a 403 status code when the route is called without a valid signature. That is the whole of the middleware contract as described: there is no redirect to a login page, no friendly error page, and no mention of a logging hook, so a request that fails validation is indistinguishable from one that was never signed.
What the package leaves out: revocation, auditing and host limits
Three gaps are worth knowing before this becomes the only thing standing between a stranger and a private file. First, a signed URL stays valid until its expiry passes, and the documentation describes no way to invalidate one early. There is no nonce, no use counter and no revocation list, so a link that leaks is a link that works for the rest of its window. Second, there is no audit trail. Validation returns a boolean and the middleware aborts with a 403, and no failure reason is recorded, so detecting a pattern of bad signatures means instrumenting the middleware yourself. Third, sign() accepts any string you hand it, and nothing in the documentation restricts which hosts a signed URL may point at. That is the same flexibility the package advertises for cross-app links, and it is also a place where a caller who builds URLs from user input could sign a link to an address nobody intended.
How this differs from Laravel's native signed routes
Laravel's built-in signed URLs bind a signature to a route in your application, using the application key. The README states the differences plainly: this package makes signed URLs easy to use between different apps, does not tie the signing secret to the app key, and can sign any URL rather than only a route belonging to your app. The trade-off runs in both directions. Native signing gives you per-route signatures and framework level helpers such as temporary signed routes, with no extra configuration. This package gives you a uniform way to sign links that leave your application, at the cost of a second secret to distribute and protect, and a middleware that speaks only in 403 responses. For a pure Laravel app whose links never cross a host boundary, the native route signing is the shorter path. There is also a framework agnostic sibling package, spatie/url-signer, for projects that are not Laravel applications.
Release cadence, the MIT licence and what an upgrade touches
The repository is not archived, and the last push was on 2026-06-26. Released versions in the recent history are 3.3.0 on 2026-02-22, 3.2.1 on 2025-02-21 and 3.2.0 on 2024-05-14, so the project has moved in small steps rather than a fast release train, and 3.x has been the shape of the API for a while. The repository also carries an UPGRADING.md file alongside CHANGELOG.md, which is where you would look first before pulling a newer minor version into an existing integration. The licence is the MIT License, and the README states that you are free to use the package; the practical obligations of that licence are a matter for your own review. The code is licensed MIT and the package is free to install in production, with no paid feature gating the signing path.
Editorial conclusion
Adopt spatie/laravel-url-signer when links must be signed across hosts or must outlive an app key rotation, and skip it when you need single-use links, an audit trail of validation attempts, or a signed URL that expires at second granularity inside a Laravel app that already handles signed routes. Before wiring it into a production flow, verify that the URL_SIGNER_SIGNATURE_KEY environment variable holds the same secret in every environment that has to validate a link, since a mismatch surfaces only as a boolean false from validate() and a 403 from the signed-url middleware.
Frequently asked questions
How do I install spatie/laravel-url-signer and set the signature key?
Install it with composer require spatie/laravel-url-signer, then run php artisan generate:url-signer-signature-key, which writes the secret into your environment files unless you pass the show option. The configuration file reads that value from the URL_SIGNER_SIGNATURE_KEY environment variable.
How long is a signed URL valid by default in spatie/laravel-url-signer?
The default_expiration_time_in_seconds setting is 60 * 60 * 24, so a signed URL lives for one day. You can change that in the config file, or pass a date or a number of seconds as the second argument to sign().
How do I validate a signed URL and protect a route with spatie/laravel-url-signer?
Call UrlSigner::validate(), which returns true or false, or register the ValidateSignature middleware as signed-url in your HTTP kernel and apply it to the route, in which case an invalid signature produces a 403 status code.
Does spatie/laravel-url-signer work outside Laravel?
The package targets Laravel applications and exposes a facade plus route middleware. For non-Laravel projects the README points to the framework agnostic version, spatie/url-signer.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/spatie-laravel-url-signer)Was this useful?