Defer Laravel email verification

Joe Vallender • March 20, 2021

If you're using Laravel, there's a good chance you're also using the built in email verification feature. It requires that a new user clicks an email link before being able to access routes protected with the verified middleware.

Sometimes though, you don't want to interupt the registration/onboarding process by forcing the user to visit their inbox, while still requiring email verificaition at some point in the near future.

Here are some simple steps to achieve that behaviour.

Since Laravel often has config options to tweak things like this, we'll quickly check for some.

Open app/Http/Kernel.php to find the location of the verified middleware.

protected $routeMiddleware = [
    // ...
    'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];

I'm using Visual Studio Code with the PHP IntelliSense plugin so I can Cmd+Click to automatically open that file. Alternatively you can click through vendor/laravel... to find it.

if (! $request->user() ||
    ($request->user() instanceof MustVerifyEmail &&
    ! $request->user()->hasVerifiedEmail())) {
    return $request->expectsJson()
            ? abort(403, 'Your email address is not verified.')
            : Redirect::guest(URL::route($redirectToRoute ?: 'verification.notice'));
}

No config options, here. Let's also check the hasVerifiedEmail() method on the trait at Illuminate\Auth\MustVerifyEmail.

public function hasVerifiedEmail()
{
    return ! is_null($this->email_verified_at);
}

Okay, no config options anywhere - so the next best solution is to copy EnsureEmailIsVerified into our app and change Kernel.php to use that instead.

Create a new file at app/Http/Middleware/EnsureEmailIsVerified.php and paste in the existing middleware code.

Remember to change the namespace namespace App\Http\Middleware; and change your Kernel.php to use the new class.

protected $routeMiddleware = [
    // ...
    'verified' => \App\Http\Middleware\EnsureEmailIsVerified::class,
];

So far we haven't changed any logic, but Laravel is now using a file in our app directory that we can safely change.

Now you can change the if clause to suit your requirements. For example if we want to allow the user 24 hours to to verify their email before restricting their access, we could use $request->user()->created_at < now()->subDay() and the resulting handle function would look like this:

public function handle($request, Closure $next, $redirectToRoute = null)
{
    if (! $request->user() ||
        ($request->user() instanceof MustVerifyEmail &&
        $request->user()->created_at < now()->subDay() &&
        ! $request->user()->hasVerifiedEmail())) {
        return $request->expectsJson()
                ? abort(403, 'Your email address is not verified.')
                : Redirect::guest(URL::route($redirectToRoute ?: 'verification.notice'));
    }

    return $next($request);
}

You could also use your own config() option to configure the interval outside the middleware code:

$request->user()->created_at < now()->subMinutes(config('myproject.verify_after'))

config/myproject.php

return [
    'verify_after' => 360 // Minutes before verification will be enforced
];

Finally, it could be worth having a prompt on screen somewhere to let the user know that email verification will eventually be required, so they aren't surprised later when the allowed interval expires.