Customising the Email Verification Expiration Time cover image

Customising the Email Verification Expiration Time

James Bannister • July 8, 2019 • 3 minute read

authentication laravel

One of the biggest benefits of using a framework like Laravel is that so many of the common use-cases or problems have already been solved; email verification is no exception.

Since Laravel 5.7, it has been possible to implement email verification out-of-the-box with Laravel.

I'll assume you already have this setup and working on your own environment.

By default, the verification email sent out by Laravel will expire in 60 minutes. This may be OK in some scenarios, however in my case this served no real purpose. Users were complaining that they were clicking the link, only for it to not work and they had to request a new verification email simply due to the old link expiring.

To work around this, there are two solutions, update the configured expiration time, or override the method that generates the verification URL.

Update the configured expiration time

This is a feature that was introduced in Laravel 5.8, so if you are still on 5.7 then skip on to the next section.

If we check out Illuminate\Auth\Notifications\VerifyEmail we can see the verificationUrl method looks like this:

protected function verificationUrl($notifiable)
{
    return URL::temporarySignedRoute(
        'verification.verify',
        Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
        ['id' => $notifiable->getKey()]
    );
}

As you can see above, when generating the temporary signed route, the framework attempts to fetch a config variable at auth.verification.expire and if it cannot find one then it will fall back to 60 minutes.

If you still want to include an expiration time in your verification URLs, then just add a config setting for the expiration time to you config/auth.php file like so:

'verification' => [
    'expire' => 525600, // One year in minutes - enter as many minutes as you would like here
],

Override the generation of the verification URL

In my case, I didn't want to just update the expiration time, I wanted to remove it entirely; in favour of a simple signed route.

To do this, we just need to extend the VerifyEmail class and override the verificationUrl method.

I have a directory in my project called Core which is where I place all my helpers, custom classes, and general application logic that doesn't fit, logically, anywhere else. You may have a similar directory or different place you wish to place this code; so just substitute as appropriate.

  1. Add a new file App/Core/Auth/VerifyEmail.php
  2. Fill this file with the below:
<?php

namespace App\Core\Auth;

use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\URL;

class VerifyEmail extends \Illuminate\Auth\Notifications\VerifyEmail
{
    protected function verificationUrl($notifiable)
    {
        return URL::signedRoute(
            'verification.verify',
            ['id' => $notifiable->getKey()]
        );
    }
}
  1. Update your User model to override the sendEmailVerificationNotification method to use your newly created class above:
/**
 * Send the email verification notification.
 *
 * @return void
 */
public function sendEmailVerificationNotification()
{
    $this->notify(new \App\Core\Auth\VerifyEmail);
}
  1. Voila! Your email verification emails will now be generated with your customised verification URL.

In my example above, I still wanted a signed route so that I could verify that the verification URL hadn't been tampered with, but you can substitute this to generate the URL any way you want. For example, let's implement the one year timeout from my first approach; simply update the verificationUrl to:

protected function verificationUrl($notifiable)
    {
        return URL::temporarySignedRoute(
            'verification.verify',
            Carbon::now()->addMinute(525600),
            ['id' => $notifiable->getKey()]
        );
    }

If you have any feedback, thoughts, or different approaches to this then I'd love to hear from you; I'm @jryd_13 on Twitter.

Thanks!