Site icon Bootsity

Implementing Two-Factor Authentication in Laravel Applications

1. Introduction

Two-factor Authentication, also known as 2FA is a type, or subset, of Multi-factor Authentication. Multi-factor authentication is a method of confirming identity using by combination of two or more claimed identities. 2FA is a method of confirming users’ claimed identities by using a combination any two different factors from below:

Essentially, this approach allows us to create a restriction for certain areas in our application. It ensures that only the right people have access to the resources in those areas. In this article we are going to take a look at how we can implement 2FA in our Laravel application in really simple steps. Also, we will be using email as our means of verification of user’s identity. Let us dive right in.

2. Setup for Two Factor Authentication

We need the following to get started:

3. The Process or Workflow

When an user tries to access a route protected by 2FA, he gets a mail notification containing an OTP code and is redirected to a form where they can input the OTP.

When this OTP is entered and is verified to be correct, they are able to access the resource, if the code is incorrect, they are not granted access.

The user session will last for the same time as Laravel’s set session lifetime. The duration for this can be found and modified in /config/sessions.php

4. Adding Two-factor Form

We are going to add a form which allows users to enter the OTP that was received in their email addresses and submit it for processing by the application’s backend. The markup for the form can be found here. The excerpt from from code is:

<form action="" method="post">
     @csrf
     <div class="form-group">
         <label for="token">Token</label>
         <input type="text" name="token" placeholder="Enter OTP" class="form-control{{ $errors->has('token') ? ' is-invalid' : '' }}" id="token"> 
           @if($errors->has('token'))
              <span class="invalid-feedback" role="alert">
                  <strong>{{ $errors->first('token') }}</strong>
              </span> 
           @endif
        </div>
    <button class="btn btn-primary btn-large">Verify</button>
</form>

5. Writing Database Migration

We have to ensure that our users’ migration contains an email field. As we are using email as of of the factor in 2FA in this article. Users migration are generally present in file  /database/migrations/<datetime>_create_users_migration.php

We need two extra fields in the users migration: two_factor_token and two_factor_expiry. We can do this by generating a new migration with this command:

php artisan make:migration add_2fa_fields_to_users_table --table=users

The command above will generate the migration and set the table as users, so we can add the following within the migration’s schema closure:

// this goes in the up() method
$table->string('two_factor_token')->nullable();
$table->datetime('two_factor_expiry')->nullable();

// this goes in the down() method
$table->dropColumn('two_factor_expiry');
$table->dropColumn('two_factor_token');

After saving this file, we will run the command: php artisan migrate to append the fields to the users table.

6. Generating Middleware and Mailables

We are adding a middleware which will serve as a filter for requests coming into the route we protect with 2FA.

To generate the middleware, run:

php artisan make:middleware TwoFactorVerification

In the handle method of this middleware, we are going to check if the current time is greater than the time in the two_factor_expiry field of the users’ migration.

The request will pass if the condition specified evaluates to true, otherwise, a OTP is generated and sent to their mail, they are redirected to the form to input the token they got via email.

Our middleware looks like:

$user = auth()->user();

if ($user->two_factor_expiry > \Carbon\Carbon::now()) {
    return $next($request);
}

$user->two_factor_token = str_random(10);
$user->save();

\Mail::to($user)->send(new TwoFactorAuthMail($user->two_factor_token));

return redirect('/2fa');

Now, we need to generate a mailable (TwoFactorAuthMail) to configure the mail to be sent to the user.

Also, do not forget to import the necessary namespaces and classes.

We can quickly generate the mailable with this command: php artisan make:mail TwoFactorAuthMail

Pass in a $token argument (or variable) to the mailable’s constructor such that in the end, the mailable looks like this:

public $token;

public function __construct($token)
{
    $this->token = $token;
}

public function build()
{
    return $this->view('email.2fa')->subject('Your Authentication Token');
}

After this, add the middleware to the route middleware array in App/Http/Kernel.php:

protected $routeMiddleware = [
    // other middlewares
    'two_factor_auth' => \App\Http\Middleware\TwoFactorVerification::class,
];

Doing this will register the middleware on the app and you can use the middleware on any route/controller you want. For example:

// in routes
Route::middleware('two_factor_auth')->group(function () {
    // routes go here
});

// in controller constructors
public function __construct()
{
    $this->middleware('two_factor_auth');
}

7. Setting up routes

The next thing we are going to do is to set up our routes – a get request to /2fa and a post request to /2fa:

Route::get('/2fa', function () {
    return view('2fa');
}

Route::post('/2fa', function (Request $request) {
    $request->validate([
        'token' => 'required',
    ]);

    if($request->input('token') == Auth::user()->two_factor_token){            
        $user = Auth::user();
        $user->two_factor_expiry = \Carbon\Carbon::now()->addMinutes(config('session.lifetime'));
        $user->save();

        return redirect()->intended('home');
    } else {
        return redirect('/2fa')->with('message', 'Incorrect code.');
    }
});

8. TwoFactorController

We can extract the closures of both the GET and POST requests above to a separate controller. Let us call the controller TwoFactorController and we shall generate it using the command php artisan make:controller TwoFactorController on the terminal.

When the controller is generated, we can have methods to display the form and another method to handle the post request as seen in this snippet:

// show the two factor auth form
public function show2faForm()
{
    return view('2fa');
}

// post token to the backend for check
public function verifyToken(Request $request)
{
    $this->validate(['token' => 'required']);

    $user = auth()->user();

    if ($request->token == $user->two_factor_token) {
        $user->two_factor_expiry = \Carbon\Carbon::now()->addMinutes(config(session.lifetime));
        $user->save();
        return redirect()->intended('/home');
    }

    return redirect('/2fa')->with('message', 'Incorrect token.');
}

 

As you can see in the POST request method, we first validate that the token was indeed entered in the form otherwise the users get a response that the token is required to proceed.

The routes will look like this after the extraction has been done:

Route::get('/2fa', 'TwoFactorController@show2faForm');
Route::post('/2fa', 'TwoFactorController@verifyToken');

Do keep in mind that the user is already authenticated or logged in on the app, he only doesn’t have access to that particular resource because of the middleware that we have set up earlier.

If the validation passes, we fetched the currently authenticated user, then checked that token entered on the form is the same as the one that was sent to the user then we set the expiry of the token to the session lifetime that was set in config/sessions.php, saved the new data to the user then redirect to our intended route or get bounced back to /home route.

9. Conclusion

In this article, we have learned how to implement two-factor authentication in Laravel applications in very simple steps. The code for this article is available on github. You may, check it out here.

If you do have questions or comments, feel free to discuss in comments.

Exit mobile version