Inviting Users with Laravel’s Singed URLs

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]']);
The first parameter is not the URI, it’s the name of the route. It means, if you want to use signed URLs for one of your route, you need to name it first.

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', '[email protected]')->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]']);
Note, we assume the routes and the controllers have been set up and ready to use.

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', '[email protected]')->name('register');
Route::post('register', '[email protected]');
<?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.

Special thanks for the following recource(s): Icon made by Freepik from www.flaticon.com