July 7th, 2022

Signed URL’s with expiration time in Laravel

Signed URL’s with expiration time in Laravel

Creating signed URLs in Laravel is as simple as creating normal routes, except few key differences. In order for Laravel to confirm that these URLs haven't been altered since they were generated, a signature hash is attached to the query string of the URL. Let’s take a look of how we can create a signed URL and set an expiration time.

To create a signed route we have to use the URL facade. Now we can use either signedRoute() or temporarySignedRoute(). In our case we want the route to expire after a certain amount of time so we’ll use the latter option. The temporarySignedRoute() method accepts two arguments. The first one will be a route name which is a reference to an already existing route. The second argument that we have to pass into the temporarySignedRoute() method will be the expiration time. Let’s see how that looks in code.

For instance, you may implement a public "unsubscribe" link that is sent to your subscribers using signed URLs. Use the signedRoute() method of the URL facade to produce a signed URL to a specified route:

1use Illuminate\Support\Facades\URL;
2 
3return URL::signedRoute('unsubscribe', ['user' => 1]);

The code above will return a string like this: https://foo.test/users/1/unsubcribe?signature=f834ed8570e05de6c50ad10bd6abcf71e9867fcb14bdf2670b4bf572ce346f3b

This is great but what we really want is to have an expiration expiration timestamp that is encoded into the signed URL so when Laravel validates the route it will ensure that the expiration timestamp has not elapsed. Ok, fair enough. To achieve that we will make use of the temporarySignedRoute() method.

1use Illuminate\Support\Facades\URL;
2 
3return URL::temporarySignedRoute(
4 'unsubscribe', now()->addMinutes(30), ['user' => 1]
5);

Now that we have the signed URL ready to use, we need to validate the incoming requests and to do that we have two options:

1 - With Middleware

Laravel offers a signed middleware that we can use. First, we have to make sure we include the middleware in App\Http\Kernel

1use Illuminate\Routing\Middleware\ValidateSignature;
2 
3protected $routeMiddleware = [
4 // ...
5 'signed' => ValidateSignature::class,
6];

Now we can use it inside our routes file like so:

1Route::post('unsubscribe/{user}', UnsubscribeUser::class)
2 ->name('unsubscribe')
3 ->middleware('signed');

2 - Using the hasValidSignature() method on the request object

1use Illuminate\Http\Request;
2 
3public function __invoke(Request $request)
4{
5 if (! $request->hasValidSignature()) {
6 //.. do something
7 }
8}

Or we can pass in a callback to our route to check the incoming request has a valid signature:

1use Illuminate\Http\Request;
2 
3Route::get('/unsubscribe/{user}', function (Request $request) {
4 if (! $request->hasValidSignature()) {
5 abort(401);
6 }
7 
8 // ...
9})->name('unsubscribe');

3 - Handling invalid signed routes

When someone visits a signed URL that has expired, they will receive a generic error page for the 403 HTTP status code. Now we can change / customize that by defining a custom "renderable" closure for the InvalidSignatureException exception in the exception handler.

1use Illuminate\Routing\Exceptions\InvalidSignatureException;
2 
3 
4/**
5 * Register the exception handling callbacks for the application.
6 *
7 * @return void
8 */
9public function register()
10{
11 $this->renderable(function (InvalidSignatureException $e) {
12 return response()->view('error.link-expired', [], 403);
13 });
14}

For more informations about the signed routes in Laravel, visit the official documentation.

Statamic Ninja

Comments

Marian Pop

PHP / Laravel Developer. Writing and maintaining @LaravelMagazine. Host of "The Laravel Magazine Podcast". Pronouns: vi/vim.

Subscribe to our newsletter

Get latest news, tutorials, community articles and podcast episodes delivered to your inbox.

Weekly articles
We send a new issue of the newsletter every week on Friday.
No spam
We'll never share your email address and you can opt out at any time.