Blog Detail

01

Dec
How to add Two Factor Authentication Easily in Laravel 8 cover image

arrow_back How to add Two Factor Authentication Easily in Laravel 8

Michael Dzjaparidze introduced a two-factor authentication package for Laravel. This package currently only works out of the box with the MessageBird Verify API or the ‘null’ driver that goes through all the steps of the two-factor authentication process without actually doing any real verification. This could be useful for testing purposes. You can, however, specify a custom provider yourself. This package uses throttling to limit the number of unsuccessful authentication attempts in a certain amount of time.

Installation

To install using Composer run:

composer require michaeldzjap/twofactor-auth

If you want to use MessageBird Verify as the two-factor authentication provider then you also need to install the MessageBird PHP API:

composer require messagebird/php-rest-api

and don’t forget to add your MESSAGEBIRD_ACCESS_KEY and TWO_FACTOR_AUTH_DRIVER=messagebird variables to the .env. If you instead wish to use the null driver (default) then do NOT define the TWO_FACTOR_AUTH_DRIVER variable in your .env.

From Laravel 7 and onwards you will also need to install the laravel/ui package:

composer require laravel/ui

Add the service provider to the providers array in config/app.php:

MichaelDzjap\TwoFactorAuth\TwoFactorAuthServiceProvider::class

Run the following artisan command to publish the configuration, language, and view files:

php artisan vendor:publish

Run the following artisan command to run the database migrations

php artisan migrate

This will add a mobile column to the users table and create a two_factor_auths table.

Add the following trait to your User model:

use MichaelDzjap\TwoFactorAuth\TwoFactorAuthenticable;

class User extends Authenticatable
{
    use Notifiable, TwoFactorAuthenticable;

Optionally, you might want to add ‘mobile’ to your $fillable array.

Changes to the Login Process

The following two-factor authentication routes will be added automatically:

$router->group([
    'middleware' => ['web', 'guest'],
    'namespace' => 'App\Http\Controllers\Auth',
], function () use ($router) {
    $router->get('/auth/token', 'TwoFactorAuthController@showTwoFactorForm')->name('auth.token');
    $router->post('/auth/token', 'TwoFactorAuthController@verifyToken');
});

The first route is the route the user will be redirected to once the two-factor authentication process has been initiated. The second route is used to verify the two-factor authentication token that is to be entered by the user. The showTwoFactorForm controller method does exactly what it says. There do exist cases where you might want to respond differently, however. For instance, instead of loading a view, you might just want to return a json response. In that case, you can simply overwrite showTwoFactorForm in the TwoFactorAuthController to be discussed below.

Add the following import to LoginController:

use MichaelDzjap\TwoFactorAuth\Contracts\TwoFactorProvider;

class LoginController extends Controller
{

and also add the following functions:

/**
 * The user has been authenticated.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  mixed  $user
 * @return mixed
 */
protected function authenticated(Request $request, $user)
{
    if (resolve(TwoFactorProvider::class)->enabled($user)) {
        return self::startTwoFactorAuthProcess($request, $user);
    }

    return redirect()->intended($this->redirectPath());
}

and

/**
 * Log out the user and start the two-factor authentication state.
 *
 * @param  \Illuminate\Http\Request $request
 * @param  \App\Models\User $user
 * @return \Illuminate\Http\Response
 */
private function startTwoFactorAuthProcess(Request $request, $user)
{
    // Logout user, but remember user id
    auth()->logout();
    $request->session()->put(
        'two-factor:auth', array_merge(['id' => $user->id], $request->only('email', 'remember'))
    );

    self::registerUserAndSendToken($user);

    return redirect()->route('auth.token');
}

and lastly

/**
 * Provider-specific two-factor authentication logic. In the case of MessageBird
 * we just want to send an authentication token via SMS.
 *
 * @param  \App\Models\User $user
 * @return mixed
 */
private function registerUserAndSendToken(User $user)
{
    // Custom, provider dependend logic for sending an authentication token
    // to the user. In the case of MessageBird Verify this could simply be
    // resolve(TwoFactorProvider::class)->sendSMSToken($this->user)
    // Here we assume this function is called from a queue'd job
    dispatch(new SendSMSToken($user));
}

You can discard the third function if you do not want to send a two-factor authentication token automatically after a successful login attempt. Instead, you might want the user to instantiate this process from the form of him/herself. In that case, you would have to add the required route and controller method to trigger this function yourself. The best place for this would be the TwoFactorAuthController to be discussed next.

Add a TwoFactorAuthController in app/Http/Controllers/Auth with the following content:

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use MichaelDzjap\TwoFactorAuth\Http\Controllers\TwoFactorAuthenticatesUsers;

class TwoFactorAuthController extends Controller
{
    use TwoFactorAuthenticatesUsers;

    /**
     * The maximum number of attempts to allow.
     *
     * @var int
     */
    protected $maxAttempts = 5;

    /**
     * The number of minutes to throttle for.
     *
     * @var int
     */
    protected $decayMinutes = 1;

    /**
     * Where to redirect users after two-factor authentication passes.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;
}

If you want to give textual feedback to the user when two-factor authentication fails due to an expired token or when throttling kicks in you may want to add this to resources/views/auth/login.blade.php:

<form class="form-horizontal" role="form" method="POST" action="{{ route('login') }}">
    @csrf

    {{-- Add this block to show an error message in case of an expired token or user lockout --}}
    @if ($errors->has('token'))
        <div class="alert alert-danger alert-dismissible fade show" role="alert">
            <strong>{{ $errors->first('token') }}</strong>
            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
    @endif

You can Visit more details on Github.

Published at : 01-12-2021

Author : Rizwan Aslam
AUTHOR
Rizwan Aslam

I am a highly results-driven professional with 12+ years of collective experience in the grounds of web application development especially in laravel, native android application development in java, and desktop application development in the dot net framework. Now managing a team of expert developers at Codebrisk.

Launch your project

Launch project