Implementing Two Factor Authentication in Laravel

 

 

In this article we will cover  How to setup Two Factor Authentication in Laravel on top of Laravel Basic Authentication. We will be using Google2FA-Laravel Package for the same. (pragmarx/google2fa-laravel)

Before Moving on, Let's first understand briefly about Two Factor Authentication.

Two Factor Authentication (2FA), often referred to as Two-Step-Verification, is a security process in which the user provides two authentication factors to verify they are who they say they are.

Two-Factor Authentication provides an additional layer of security that makes it harder for attackers to gain access to person’s device and online accounts because knowing the victim’s password is alone is not enough to pass the authentication check.


As we dig into the steps, make sure you have following ready.

Alright Let's dig into the step.


 

Install Required Composer Packages

First let's setup the required packages,  this will help us implement Google2FA backed two factor Authentication into our project. Open you terminal move to the project root.

Execute following commands.

composer require pragmarx/google2fa-laravel

 

composer require bacon/bacon-qr-code

 

bacon-qr composer laravel

Let's go ahead and publish the ServiceProvider class which is installed under pragmarx/google2fa-laravel package.

php artisan vendor:publish --provider="PragmaRX\Google2FALaravel\ServiceProvider"

New Model, Migration, Controller and Database Table for 2FA

Next we need the following

  • A database table to store the data related to two factor authorization.
  • A Model file against the table.
  • A Controller file to handle 2FA

Let's create all of these with a single command.

 

php artisan make:model PasswordSecurity -m -c

 

We are naming our new Model PasswordSecurity, this table will be used to store two factor secret and you can also use it to store user specific advanced password security properties.

This command will generate following things for you

  • A Model named PasswordSecurity.php under App directory
  • A migration file create_password_securities under Database / Migrations directory
  • A Controller file PasswordSecurityController.php under App / Http / Controllers directory

Let' start adding the required fields to the migration file create_password_securities

    public function up()
    {
        Schema::create('password_securities', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id');
            $table->boolean('google2fa_enable')->default(false);
            $table->string('google2fa_secret')->nullable();
            $table->timestamps();
        });
    }

We have added three fields to the migration file

  • A user_id field which will be the foriegn key from the User table.
  • A boolean field google2fa_enable with default value of false, This will be used to enable/ disable 2FA authentication on user account.
  • A string field google2fa_secret which will be used to store the random secret key used to authenticate and verify user's 2FA Authentication.

Let's run the migration to create the tables for us.

php artisan migrate:refresh

 

You should see the password_securities table in your database with following structure.


 

Defining Eloquent Relationship

Since now we have our database tables ready, Let's go ahead and assign relationship between User and PasswordSecurity model. Every user will have exactly one row in password_securities table.

Thus we will assign one-to-one eloquent relationship between these two models.

Open User.php model file located at App directory and add following code into it.

    public function passwordSecurity()
    {
        return $this->hasOne('App\PasswordSecurity');
    }

 

Open PasswordSecurity.php model file located at App directory and add following code into it.

 

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class PasswordSecurity extends Model
{

    protected $guarded = [];

    public function user()
    {
        return $this->belongsTo('App\User');
    }
}

Routes and Controller Method to Enable / Disable 2FA

Most of the applications allow their customers to Enable or Disable 2FA on their account.

Let's create a 2FA Settings page where user can enable/disable the 2FA on his account, to enable user should scan the QR code and enter the OTP generated in Google Authenticator Mobile App.

Thus if 2FA is enabled, user will need to provide a one time password after they successfully login with their username / password combination.

Let's create necessary routes, controller method and view files to implement this functionality.

Open your web.php file under Routes folder and add following routes into it.

Route::get('/2fa','PasswordSecurityController@show2faForm');
Route::post('/generate2faSecret','PasswordSecurityController@generate2faSecret')->name('generate2faSecret');
Route::post('/2fa','PasswordSecurityController@enable2fa')->name('enable2fa');
Route::post('/disable2fa','PasswordSecurityController@disable2fa')->name('disable2fa');

 

We will add Controller methods in PasswordSecurityController.php Controller file, Let's look into the Controller method's one by one

show2faform()

    public function show2faForm(Request $request){
        $user = Auth::user();

        $google2fa_url = "";
        if($user->passwordSecurity()->exists()){
            $google2fa = app('pragmarx.google2fa');
            $google2fa_url = $google2fa->getQRCodeGoogleUrl(
                '5Balloons 2A DEMO',
                $user->email,
                $user->passwordSecurity->google2fa_secret
            );
        }
        $data = array(
            'user' => $user,
            'google2fa_url' => $google2fa_url
        );
        return view('auth.2fa')->with('data', $data);
    }

This method returns the Form view to user from where he can enable or disable the 2FA.

For the first time Enabling 2FA is a two step process.

  1. Secret Key Generation ( We create an entry into password_securiries table and store Secrey key into it)
  2. Verifiying OTP form Google Authenticator App (We ask user to enter code from Google Authenticator, to finally enable the 2FA on his account)

Thus in show2faform method we check if user has the google secret key generated (if(count($user->passwordSecurity))), If yes we proceed to show him the QR code.

[caption id="attachment_970" align="aligncenter" width="1202"]2FA-Laravel-Step1 Laravel 2FA Step 1[/caption]

Clicking on Generate Secret Key Button makes a post request to generate2faSecret() method

generate2faSecret()

public function generate2faSecret(Request $request){
    $user = Auth::user();
    // Initialise the 2FA class
    $google2fa = app('pragmarx.google2fa');

    // Add the secret key to the registration data
    PasswordSecurity::create([
        'user_id' => $user->id,
        'google2fa_enable' => 0,
        'google2fa_secret' => $google2fa->generateSecretKey(),
    ]);

    return redirect('/2fa')->with('success',"Secret Key is generated, Please verify Code to Enable 2FA");
}

If user are looking to Enable 2FA for first time , they need to generate a secret. With this method we create an entry into password_securities table with the generated secret key.

[caption id="attachment_971" align="aligncenter" width="1178"]2FA-Laravel-Step2 Laravel Enable 2FA Step 2[/caption]

Enter the OTP from Google Authenticator App and on Clicking on Enable 2FA Button makes a post request to enable2fa() method

enable2fa()

    public function enable2fa(Request $request){
        $user = Auth::user();
        $google2fa = app('pragmarx.google2fa');
        $secret = $request->input('verify-code');
        $valid = $google2fa->verifyKey($user->passwordSecurity->google2fa_secret, $secret);
        if($valid){
            $user->passwordSecurity->google2fa_enable = 1;
            $user->passwordSecurity->save();
            return redirect('2fa')->with('success',"2FA is Enabled Successfully.");
        }else{
            return redirect('2fa')->with('error',"Invalid Verification Code, Please try again.");
        }
    }

Once the secret key is generated, as the second step user need to enter the OTP from Google Authenticator App and verify it to finally enable the 2FA.

[caption id="attachment_972" align="aligncenter" width="1194"]2FA Enabled Laravel 2FA Enabled Laravel[/caption]

To disable 2FA, user need to Enter the current account password and clicking on Disable 2FA Button makes a POST request to disable2fa() method

disable2fa()

    public function disable2fa(Request $request){
        if (!(Hash::check($request->get('current-password'), Auth::user()->password))) {
            // The passwords matches
            return redirect()->back()->with("error","Your  password does not matches with your account password. Please try again.");
        }

        $validatedData = $request->validate([
            'current-password' => 'required',
        ]);
        $user = Auth::user();
        $user->passwordSecurity->google2fa_enable = 0;
        $user->passwordSecurity->save();
        return redirect('/2fa')->with('success',"2FA is now Disabled.");
    }

Once enabled, User's see the option of disabling the 2FA on their account. But for this they need to provide the Current password on profile to make this action.

Create a new view file 2fa.blade.php under resources / views / auth folder , this will handle the triggering the above controller methods.

 

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading"><strong>Two Factor Authentication</strong></div>
                       <div class="panel-body">
                           <p>Two factor authentication (2FA) strengthens access security by requiring two methods (also referred to as factors) to verify your identity. Two factor authentication protects against phishing, social engineering and password brute force attacks and secures your logins from attackers exploiting weak or stolen credentials.</p>
                           <br/>
                           <p>To Enable Two Factor Authentication on your Account, you need to do following steps</p>
                           <strong>
                           <ol>
                               <li>Click on Generate Secret Button , To Generate a Unique secret QR code for your profile</li>
                               <li>Verify the OTP from Google Authenticator Mobile App</li>
                           </ol>
                           </strong>
                           <br/>

                       @if (session('error'))
                            <div class="alert alert-danger">
                                {{ session('error') }}
                            </div>
                        @endif
                        @if (session('success'))
                            <div class="alert alert-success">
                                {{ session('success') }}
                            </div>
                        @endif


                            @if(!count($data['user']->passwordSecurity))
                               <form class="form-horizontal" method="POST" action="{{ route('generate2faSecret') }}">
                                   {{ csrf_field() }}
                                    <div class="form-group">
                                        <div class="col-md-6 col-md-offset-4">
                                            <button type="submit" class="btn btn-primary">
                                               Generate Secret Key to Enable 2FA
                                            </button>
                                        </div>
                                    </div>
                               </form>
                            @elseif(!$data['user']->passwordSecurity->google2fa_enable)
                               <strong>1. Scan this barcode with your Google Authenticator App:</strong><br/>
                               <img src="{{$data['google2fa_url'] }}" alt="">
                           <br/><br/>
                               <strong>2.Enter the pin the code to Enable 2FA</strong><br/><br/>
                               <form class="form-horizontal" method="POST" action="{{ route('enable2fa') }}">
                               {{ csrf_field() }}

                               <div class="form-group{{ $errors->has('verify-code') ? ' has-error' : '' }}">
                                   <label for="verify-code" class="col-md-4 control-label">Authenticator Code</label>

                                   <div class="col-md-6">
                                       <input id="verify-code" type="password" class="form-control" name="verify-code" required>

                                       @if ($errors->has('verify-code'))
                                           <span class="help-block">
                                        <strong>{{ $errors->first('verify-code') }}</strong>
                                    </span>
                                       @endif
                                   </div>
                               </div>
                                   <div class="form-group">
                                       <div class="col-md-6 col-md-offset-4">
                                           <button type="submit" class="btn btn-primary">
                                               Enable 2FA
                                           </button>
                                       </div>
                                   </div>
                               </form>
                           @elseif($data['user']->passwordSecurity->google2fa_enable)
                               <div class="alert alert-success">
                                   2FA is Currently <strong>Enabled</strong> for your account.
                               </div>
                               <p>If you are looking to disable Two Factor Authentication. Please confirm your password and Click Disable 2FA Button.</p>
                               <form class="form-horizontal" method="POST" action="{{ route('disable2fa') }}">
                               <div class="form-group{{ $errors->has('current-password') ? ' has-error' : '' }}">
                                   <label for="change-password" class="col-md-4 control-label">Current Password</label>

                                   <div class="col-md-6">
                                       <input id="current-password" type="password" class="form-control" name="current-password" required>

                                       @if ($errors->has('current-password'))
                                           <span class="help-block">
                                        <strong>{{ $errors->first('current-password') }}</strong>
                                    </span>
                                       @endif
                                   </div>
                               </div>
                               <div class="col-md-6 col-md-offset-5">

                                       {{ csrf_field() }}
                                   <button type="submit" class="btn btn-primary ">Disable 2FA</button>
                               </div>
                               </form>
                            @endif
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

 

Two Step Verification with 2FA Middleware

google2fa-laravel package provides us with a middleware that can be directly used to enable the two factor Authentication. However at this point if we include the middleware directly it will enable the 2FA on all accounts.

Since we are looking to enable Two Factor only on those accounts which has 2FA enabled in their profile, We need to extend package's Authenticator class for that.

Create a Support directory under App folder and create a new Class Google2FAAuthenticator.php in it, This class will extend the Authenticator class of google2fa-laravel package.

<?php

namespace App\Support;

use PragmaRX\Google2FALaravel\Support\Authenticator;

class Google2FAAuthenticator extends Authenticator
{
    protected function canPassWithoutCheckingOTP()
    {
          if(!count($this->getUser()->passwordSecurity))
              return true;
          return
            !$this->getUser()->passwordSecurity->google2fa_enable ||
            !$this->isEnabled() ||
            $this->noUserIsAuthenticated() ||
            $this->twoFactorAuthStillValid();
    }

    protected function getGoogle2FASecretKey()
    {
        $secret = $this->getUser()->passwordSecurity->{$this->config('otp_secret_column')};

        if (is_null($secret) || empty($secret)) {
            throw new InvalidSecretKey('Secret key cannot be empty.');
        }

        return $secret;
    }


}

 

We have modified canPassWithoutCheckingOTP to exclude accounts which does not have 2FA Enabled.
We have also modified getGoogle2FASecretKey to modify the location of the column where we store our secret key.

Let's Create a new Middleware class that will refer to our extended Google2FAAuthenticator class.

Create a new class Google2FAMiddleware.php in directory App / Http / Middleware with following code.

<?php

namespace App\Http\Middleware;

use App\Support\Google2FAAuthenticator;
use Closure;

class Google2FAMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $authenticator = app(Google2FAAuthenticator::class)->boot($request);

        if ($authenticator->isAuthenticated()) {
            return $next($request);
        }

        return $authenticator->makeRequestOneTimePasswordResponse();
    }
}

Register the Middleware in Kernel.php

Now, let's register the newly created middleware in Kernel.php

protected $routeMiddleware = [
    ...
    '2fa' => \App\Http\Middleware\Google2FAMiddleware::class,
];

Include the Middleware to Controller or Route which you want under 2FA

public function __construct()
    {
        $this->middleware(['auth', '2fa'] );
    }

or

Route::get('/admin', function () {
    return view('admin.index');
})->middleware(['auth', '2fa']);

 

We are almost there, Just a couple of things more.

 

Next, let's modify the config / google2fa.php config file to change the view file, which will be shown to user as a two step verification.

'view' => 'auth.google2fa',

Let's go ahead and create a new view file named google2fa.blade.php under views/ auth directory.

 

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Two Factor Authentication</div>
                       <div class="panel-body">
                           <p>Two factor authentication (2FA) strengthens access security by requiring two methods (also referred to as factors) to verify your identity. Two factor authentication protects against phishing, social engineering and password brute force attacks and secures your logins from attackers exploiting weak or stolen credentials.</p>

                       @if (session('error'))
                            <div class="alert alert-danger">
                                {{ session('error') }}
                            </div>
                        @endif
                        @if (session('success'))
                            <div class="alert alert-success">
                                {{ session('success') }}
                            </div>
                        @endif

                               <strong>Enter the pin from Google Authenticator Enable 2FA</strong><br/><br/>
                           <form class="form-horizontal" action="{{ route('2faVerify') }}" method="POST">
                               {{ csrf_field() }}
                               <div class="form-group{{ $errors->has('one_time_password-code') ? ' has-error' : '' }}">
                                   <label for="one_time_password" class="col-md-4 control-label">One Time Password</label>
                                   <div class="col-md-6">
                                       <input name="one_time_password" class="form-control"  type="text"/>
                                   </div>
                               </div>
                               <div class="form-group">
                                   <div class="col-md-6 col-md-offset-4">
                                        <button class="btn btn-primary" type="submit">Authenticate</button>
                                   </div>
                               </div>
                           </form>

                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Two Factor Authentication</div>
                       <div class="panel-body">
                           <p>Two factor authentication (2FA) strengthens access security by requiring two methods (also referred to as factors) to
 verify your identity. 
Two factor authentication protects against phishing, social engineering and password brute force attacks and secures your logins from attackers 
exploiting weak or stolen credentials.</p>

                       @if (session('error'))
                            <div class="alert alert-danger">
                                {{ session('error') }}
                            </div>
                        @endif
                        @if (session('success'))
                            <div class="alert alert-success">
                                {{ session('success') }}
                            </div>
                        @endif

                               <strong>Enter the pin from Google Authenticator Enable 2FA</strong><br/><br/>
                           <form class="form-horizontal" action="{{ route('2faVerify') }}" method="POST">
                               {{ csrf_field() }}
                               <div class="form-group{{ $errors->has('one_time_password-code') ? ' has-error' : '' }}">
                                   <label for="one_time_password" class="col-md-4 control-label">One Time Password</label>
                                   <div class="col-md-6">
                                       <input name="one_time_password" class="form-control"  type="text"/>
                                   </div>
                               </div>
                               <div class="form-group">
                                   <div class="col-md-6 col-md-offset-4">
                                        <button class="btn btn-primary" type="submit">Authenticate</button>
                                   </div>
                               </div>
                           </form>

                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

This view will be shown to the user just after they login successfully. They will have to enter OTP from Google Authenticator Mobile App to login into the system.

We need to include the new route in our web.php routes file

Route::post('/2faVerify', function () {
    return redirect(URL()->previous());
})->name('2faVerify')->middleware('2fa');

This route will take the post request and will verify the One Time Password, If correct it will forward user to the desired URL.

 


 

Demo

[caption id="attachment_976" align="aligncenter" width="600"]Laravel Two Factor Authentication (Google2FA) Demo Laravel Two Factor Authentication (Google2FA) Demo[/caption]

 

Repo

Code Repo

There you go ! Hope you had fun Implementing Two Factor Authentication in Laravel.

Comments