Laravel is the most popular MVC framework in PHP, and there is a reason for that — Laravel never stopped improvising. One of the other major plus points to the framework is that security is never taken lightly. Thus, with the latest release of version 5.6.12,  they have introduced “Signed Routes” feature.  Signed routes are Laravel’s way to provide protection against URL manipulation by generating a signed URL with a signature hash.

Application

Let’s say we have a web application where users can subscribe to emails of various topics. Obviously, we would provide a link in every email for the user to unsubscribe from the topic’s mailing list. An example link URL to unsubscribe would look something like this:

https://www.example.com/23/unsubscribe/culinary

Here, “23 ” is ID of the user to unsubscribe,  and the topic to unsubscribe from is “culinary”. Now, a user with malicious intent may take this URL, modify its parameters, and submit a request. This would cause a scenario of unintended action being taken such as unsubscribing other users which surely we would want to avoid. With the signed URL generation, we can restrict requests from tampered URLs.

Signed URLs are especially useful for routes that are publicly accessible yet need a layer of protection against URL manipulation. Other applications include polls, activation link URLs without having to save activation code, features that require action from users, etc.

Setting it up!

First, if you haven’t yet updated to the latest Laravel version of 5.6.12, then you will have to run composer update and pull in the latest changes.

composer update laravel/framework

Next, you will need to register the new ValidateSignature middleware by adding it to your route middleware in /app/Http/Kernel.php like so:

protected $routeMiddleware = [     // ...     'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,     'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,     'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, ];

Done! Now you are all set to add signed URLs, and use “signed” middleware to validate a signed URL.

Before Signed Routes

Let us consider the example of “Unsubscribing from mailing list” mentioned above in the article. In this example, we will have a named route “unsubscribe” in our routes/web.php file.

Route::get('{id}/unsubscribe/{category}', function($id, $category) {
  // Remove the user from category's mailing list.
}) - > name('unsubscribe');

To generate a URL for this route without a signature hash, we use URL facade like so,

use\ Illuminate\ Support\ Facades\ URL;
Url::route('unsubscribe', ['id' => 23, 'category' => 'culinary']);

which will generate the intended URL like,

https://www.example.com/23/unsubscribe/culinary

As we discussed earlier, this URL is prone to manipulation which is not ideal. Thus, the use of signed routes.

Generating a Signed URL

We can generate a signed URL for any named route in our application. We will continue using our example. To generate a signed URL for our unsubscribe link, we will use the new method signedRoute of URL generator like so,

use\ Illuminate\ Support\ Facades\ URL;
Url::signedRoute('unsubscribe', ['id' => 23, 'category' => 'culinary']);

The above line of code will generate a signed URL with a signature hash like,

https://www.example.com/23/unsubscribe/culinary? signature=30b3458d01860dfg0f7ca45fH2c6657ur16a98d23478dgc9789de3c21es13ff3

Note that the URL is intact with its parameters, and Laravel has just added a signature to the query string.

Next, we will need to add signed middleware to our route definition like so,

Route::get('{id}/unsubscribe/{category}', function($id, $category) {
  // Remove the user from category's mailing list. 
}) - > name('unsubscribe') - > middleware('signed');

The signed middleware will validate signature hash of every request made to this route.

Alternatively, you may call the hasValidSignature method on the incoming Request like so,

use Illuminate\ Http\ Request;
Route::get('{id}/unsubscribe/{category}', function(Request $request, $id, $category) {
  if (!$request - > hasValidSignature()) {
    abort(401);
  }
  // ... 
}) - > name('unsubscribe');

Now by using this signed URL if a malicious user tries to tamper with the user ID,  Laravel will throw an Illuminate\Routing\Exceptions\InvalidSignatureException.

Signed URLs that expire

In addition to just signing a URL, Laravel gives us a great way to add an expiration to a signature as well. In some cases, we would want a URL to expire after some time like in case of polls. Let us make our unsubscribe link expire after one hour. To generate a temporary signed route URL that expires, you may use the temporarySignedRoute method:

use\ Illuminate\ Support\ Facades\ URL;
URL::temporarySignedRoute('unsubscribe', now() - > addHour(), ['id' => 23, 'category' => 'culinary']);

This will generate a signed URL as follows:

https://www.example.com/23/unsubscribe/culinary?expires=1524636982 &signature=30b3458d01860dfg0f7ca45fH2c6657ur16a98d23478dgc9789de3c21es13ff3

As you can see, this URL has an extra query string parameter that has the expiry timestamp.

Learn More

You may take a look at the Laravel Documentation for additional information on signed routes.