We can handle user invitations easily with the old and good database way. We create an invitation, store it with a unique token, then email it to the user. If the user uses the link, we can delete it from the database and that’s all. Now let’s give a try to Laravel’s signed URLs to handle a database-less solution.
Getting Started with Signed Routes
The basic method, how signed routes work is simple. We have a base route and with appending parameters to it, we can also calculate a hash and attach to the query string.
When we visit the route, Laravel calculates the hash behind the scenes and compare it to the attached one. If they match then the URL is valid otherwise it’s invalid. It means if any of the parameters change, the hash will be different from the original one.
There are two types of signed URLs. The first one is validated by only the given parameters, the second one has an additional parameter that contains the timestamp of the expiration. If the current timestamp is bigger than the one in the URL, it becomes invalid. If we try to change the timestamp, the hash will be different.
If the URL is invalid, Laravel will return a 403 – Unauthorized response automatically.
Generating Signed Routes
For the signed route generation, we can use the Illuinate\Support\Facades\URL facade. By using the signedRoute and the temporarySignedRoute methods, we can generate the URL we want.
// Basic URL URL::signedRoute('register', ['email' => '[email protected]']); // Expiring URL URL::temporarySignedRoute('register', now()->addDays(7), ['email' => '[email protected]']);
For the expiring URL, we can use the now() helper function to determine the expiration of the link.
Using the “signed” Middleware
We need to use the built-in signed middleware to let Laravel checking the signatures behind the scenes. As usual, we can add it where we define the route or in the controller’s constructor.
Route::get('register', 'RegisterController@register')->name('register')->middleware('signed');
Alternatively, we can use the hasValidSignature method of the current request to validate the signature. It can be a good option if you want to use some custom logic instead of the default middleware.
if ($request->hasValidSignature()) { // }
Inviting the Users by their Email
Like in the examples above, we will use the user’s email address for the signature, also we want the link to expire in 24 hours.
URL::temporarySignedRoute('register', now()->addDay(), ['email' => '[email protected]']);
All we have to do, to send the link to the user. If you are new to Laravel’s mailer solution, it’s time to read the documentation.
Handling the Registrations
Basically, we want to do two things. Show the registration form and process the registration. We can do both on the same URL because we will use different request types. We will use GET for the form and POST for processing the data.
Route::get('register', 'RegisterController@register')->name('register'); Route::post('register', 'RegisterController@process');
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class RegisterController extends Controller { public function __construct() { $this->middleware('signed'); } public function register() { return view('register.form'); } public function process(Request $request) { if ($request->input('email') !== $request->query('email')) { abort(403); } // Register the user... } }
As we see, the controller is very simple. First, we show the form and in the process method, we register the user. Since we can use the same URL for the registration – soon we will see how – we can compare the query string’s email attribute and the one that was sent with the form data. Also, we have the signed middleware for all of our routes, if the URL is expired Laravel will automatically about the process.
Quickly let’s take a look at the form’s markup:
<form action="{{ url()->full() }}" method="POST"> @csrf <input type="email" name="email"> <!-- ... --> </form>
With this technique we can use the query parameters as well, it means from the controller we can reach the query parameters with the request instance (like we did).
Summary
This was just a very simple and basic intruduction of the signed URLs. Of course, we can use this approach for many other features. It’s a nice way to implement something that does not require a database record for validating the data.
If you are interested you can read the documentation about the signed routes: https://laravel.com/docs/master/urls#signed-urls.