URL signing in Laravel
The project I'm currently working on will have to send out mails to all its users on a regular basis. It's not a newsletter: the contents of each mail will be very specific to each user. The mail also should contain a link to unsubscribe the user from simular future mails.
The link could look like this: https://myapp.com/unsubscribe
. Clicking on the link would direct the user to a login page. After the user has logged in the unsubscribe can be automatically performed. In my mind requiring the user to login first in order to unsubscribe from something isn't very user friendly.
This can be improved by adding the id of the user to the link. Here's what that could look like: https://myapp.com/user/1/unsubscribe
. With this link you the app can unsubscribe the user with id 1 in one go. That'll work, but it's not very secure. Unsubscribe links for all other users can be easily guessed. Such links can be made more secure by adding a signature and an expiry date on them.
My colleague Sebastian coded up a Laravel package to create signed url's with a limited lifetime. Here's example where the url gets signed and made valid for only one day:
echo UrlSigner::sign('https://myapp.com/user/1/unsubscribe', 1);
This outputs an url that looks like: https://myapp.com/user/1/unsubscribe?expires=1123690544&signature=93e02326d75
The validate
-method can be used the determine if a signed url is (still) valid:
$isValidUrl = UrlSigner::validate($theSignedUrlInTheExampleAbove);
The signature is calculated using the original url itself, the expiration date and a secret string that's specific to your project. When a malicious user tries to change any part of the url the signature won't match up.
I'm assuming that the most common use case of signing url's is to protect routes. The package supplies a middleware that protects routes from invalid signed url's. In the following example only requests with a valid signed url will hit the controller:
Route::get('unsubscribe', ['middleware' => 'signedurl', 'uses => 'UserController@unsubscribe']);
If you're interested in using the package, take a look at it on GitHub: https://github.com/spatie/laravel-url-signer
There's also a framework agnostic version: https://github.com/spatie/url-signer
E-mails can be intercepted and are never 100% secure. Bearing that fact in mind you should never use this kind of link for any destructive action.
EDIT: Some fellow developers pointed out that I could also obfuscate the id in the url. Here are two good libraries to do that:
When using obfuscation of the id this url `https://myapp.com/user/1/unsub `would become something like: `https://myapp.com/user/kwxgqu5w/unsub`And sure enough, the unsubscribe links of other users cannot be easily guessed. A small downside however is that the url becomes less readable. The big disadvantage is that the url will remain valid forever. As these links through an unsafe medium I think it's a good idea to give them a limited lifetime. Signing an url will do that.
What are your thoughts on "URL signing in Laravel"?