Laravel Two Factor Authentication with Google Authenticator

Last modified on April 9, 2020 7 min read

Hello, today I’m going to share how to set up two-factor authentication with Google Authenticator in Laravel. I’m testing on Laravel 7.0.

I assume that you’ve installed a Laravel app & config basic things. If you didn’t do those things, you can check this article first.

Note: You’ve to install ImageMagick on your server if not installed. You may check this:

Table of Contents

  1. Install Required Package
  2. Create Model, Migration & Controller
  3. Setup Eloquent Relationship
  4. Setup Controller
  5. Create 2FA Middleware
  6. Define Routes
  7. Create Blade Files
  8. Run Project & Test 2FA

Install Required Package

We’ll use pragmarx/google2fa-laravel package. Run this composer command to install this package:

composer require pragmarx/google2fa-laravel

After installation successful, run this command to publish the package’s config file:

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

Create Model, Migration & Controller

We need a model, migration & controller for 2FA. Let’s create all using this command:

php artisan make:model LoginSecurity -m -c

Now open create_login_securities_table migration file from database/migrations directory and update up() function like this:

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

Run the migration to create tables:

php artisan migrate:refresh

Setup Eloquent Relationship

Open User model from app folder and insert this function:

User.php
public function loginSecurity()
{
    return $this->hasOne('App\LoginSecurity');
}

Open LoginSecurity model and insert user() function & $fillable array:

LoginSecurity.php
protected $fillable = [
    'user_id'
];

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

Setup Controller

Open from LoginSecurityController from app/Http/Controllers directory and paste this code:

LoginSecurityController.php
<?php

namespace App\Http\Controllers;

use App\LoginSecurity;
use Auth;
use Hash;
use Illuminate\Http\Request;

class LoginSecurityController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Show 2FA Setting form
     */
    public function show2faForm(Request $request){
        $user = Auth::user();
        $google2fa_url = "";
        $secret_key = "";

        if($user->loginSecurity()->exists()){
            $google2fa = (new \PragmaRX\Google2FAQRCode\Google2FA());
            $google2fa_url = $google2fa->getQRCodeInline(
                'MyNotePaper Demo',
                $user->email,
                $user->loginSecurity->google2fa_secret
            );
            $secret_key = $user->loginSecurity->google2fa_secret;
        }

        $data = array(
            'user' => $user,
            'secret' => $secret_key,
            'google2fa_url' => $google2fa_url
        );

        return view('auth.2fa_settings')->with('data', $data);
    }

    /**
     * Generate 2FA secret key
     */
    public function generate2faSecret(Request $request){
        $user = Auth::user();
        // Initialise the 2FA class
        $google2fa = (new \PragmaRX\Google2FAQRCode\Google2FA());

        // Add the secret key to the registration data
        $login_security = LoginSecurity::firstOrNew(array('user_id' => $user->id));
        $login_security->user_id = $user->id;
        $login_security->google2fa_enable = 0;
        $login_security->google2fa_secret = $google2fa->generateSecretKey();
        $login_security->save();

        return redirect('/2fa')->with('success',"Secret key is generated.");
    }

    /**
     * Enable 2FA
     */
    public function enable2fa(Request $request){
        $user = Auth::user();
        $google2fa = (new \PragmaRX\Google2FAQRCode\Google2FA());

        $secret = $request->input('secret');
        $valid = $google2fa->verifyKey($user->loginSecurity->google2fa_secret, $secret);

        if($valid){
            $user->loginSecurity->google2fa_enable = 1;
            $user->loginSecurity->save();
            return redirect('2fa')->with('success',"2FA is enabled successfully.");
        }else{
            return redirect('2fa')->with('error',"Invalid verification Code, Please try again.");
        }
    }

    /**
     * Disable 2FA
     */
    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->loginSecurity->google2fa_enable = 0;
        $user->loginSecurity->save();
        return redirect('/2fa')->with('success',"2FA is now disabled.");
    }
}

In the controller, we’ve set 2FA settings form, generate secret key function and enable & disable option.

Create 2FA Middleware

This package provides us with a middleware that can be directly used to enable two-factor authentication.

Go to the app folder and create a directory named Support. Under the support folder, create a file called Google2FAAuthenticator.php & paste this code:

Google2FAAuthenticator.php
<?php

namespace App\Support;

use PragmaRX\Google2FALaravel\Support\Authenticator;

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

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

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

        return $secret;
    }

}

After that create a middleware named LoginSecurityMiddleware using this command:

php artisan make:middleware LoginSecurityMiddleware

Open the middleware from app/Http/Middleware and paste this code:

LoginSecurityMiddleware.php
<?php

namespace App\Http\Middleware;

use App\Support\Google2FAAuthenticator;
use Closure;

class LoginSecurityMiddleware
{
    /**
     * 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();
    }
}

Now open the Kernel.php from the app/Http directory & register our middleware to $routeMiddleware array.

Kernel.php
protected $routeMiddleware = [
    // ---------
    '2fa' => \App\Http\Middleware\LoginSecurityMiddleware::class,
];

Define Routes

Go to the routes folder, open web.php & define these routes:

web.php
Route::group(['prefix'=>'2fa'], function(){
    Route::get('/','[email protected]');
    Route::post('/generateSecret','[email protected]')->name('generate2faSecret');
    Route::post('/enable2fa','[email protected]')->name('enable2fa');
    Route::post('/disable2fa','[email protected]')->name('disable2fa');

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

// test middleware
Route::get('/test_middleware', function () {
    return "2FA middleware work!";
})->middleware(['auth', '2fa']);

Create Blade Files

We’re about to finish. We need to create two view files only. Navigate to resources/views/auth directory and create 2 files named 2fa_settings.blade.php and 2fa_verify.blade.php.

Open 2fa_settings blade file and paste this code:

2fa_settings.blade.php
@extends('layouts.app')
@section('content')
    <div class="container">
        <div class="row justify-content-md-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header"><strong>Two Factor Authentication</strong></div>
                    <div class="card-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

                        @if($data['user']->loginSecurity == null)
                            <form class="form-horizontal" method="POST" action="{{ route('generate2faSecret') }}">
                                {{ csrf_field() }}
                                <div class="form-group">
                                    <button type="submit" class="btn btn-primary">
                                        Generate Secret Key to Enable 2FA
                                    </button>
                                </div>
                            </form>
                        @elseif(!$data['user']->loginSecurity->google2fa_enable)
                            1. Scan this QR code with your Google Authenticator App. Alternatively, you can use the code: <code>{{ $data['secret'] }}</code><br/>
                            <img src="{{$data['google2fa_url'] }}" alt="">
                            <br/><br/>
                            2. Enter the pin from Google Authenticator app:<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="secret" class="control-label">Authenticator Code</label>
                                    <input id="secret" type="password" class="form-control col-md-4" name="secret" required>
                                    @if ($errors->has('verify-code'))
                                        <span class="help-block">
                                        <strong>{{ $errors->first('verify-code') }}</strong>
                                        </span>
                                    @endif
                                </div>
                                <button type="submit" class="btn btn-primary">
                                    Enable 2FA
                                </button>
                            </form>
                        @elseif($data['user']->loginSecurity->google2fa_enable)
                            <div class="alert alert-success">
                                2FA is currently <strong>enabled</strong> on 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') }}">
                                {{ csrf_field() }}
                                <div class="form-group{{ $errors->has('current-password') ? ' has-error' : '' }}">
                                    <label for="change-password" class="control-label">Current Password</label>
                                        <input id="current-password" type="password" class="form-control col-md-4" name="current-password" required>
                                        @if ($errors->has('current-password'))
                                            <span class="help-block">
                                        <strong>{{ $errors->first('current-password') }}</strong>
                                        </span>
                                        @endif
                                </div>
                                <button type="submit" class="btn btn-primary ">Disable 2FA</button>
                            </form>
                        @endif
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Then open 2fa_verify blade file and paste:

2fa_verify.blade.php
@extends('layouts.app')
@section('content')
    <div class="container">
        <div class="row justify-content-md-center">
            <div class="col-md-8 ">
                <div class="card">
                    <div class="card-header">Two Factor Authentication</div>
                    <div class="card-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 ($errors->any())
                            <div class="alert alert-danger">
                                <ul>
                                    @foreach ($errors->all() as $error)
                                        <li>{{ $error }}</li>
                                    @endforeach
                                </ul>
                            </div>
                        @endif

                        Enter the pin from Google Authenticator app:<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="control-label">One Time Password</label>
                                <input id="one_time_password" name="one_time_password" class="form-control col-md-4"  type="text" required/>
                            </div>
                            <button class="btn btn-primary" type="submit">Authenticate</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Last task, open config/google2fa.php file and set one-time password view file path:

google2fa.php
/*
 * One Time Password View.
 */
'view' => 'auth.2fa_verify',

This view will be shown to the user to enter OTP.

Run Project & Test 2FA

Our application is ready. Let’s run the project and test 2FA. After running the app, login to app and visit this route:

http://exmaple.com/2fa

If everything is okay, you’ll see a page like this:

Then click the “Generate Secret Key…” button. It’ll generate a key and display this info:

Open the Google Authenticator application and scan the QR code. Then the app will show a code. Enter the code to enable 2FA.

After entering the correct code, the 2FA will be enabled on your account.

Let’s test middleware: Logout and login again. Then visit this route:

http://exmaple.com/test_middleware

It’ll ask to provide OTP like:

That’s it. You’ll find more functionalities from pragmarx/google2fa-laravel‘s GitHub repo.

The tutorial is over. You can download this project from GitHub. Thank you. 🙂

Monthly Newsletter

One email a month, packed with the latest tutorials, delivered straight to your inbox.
We'll never send any spam or promotional emails.
Author

Hey, I'm Md Obydullah. I build open-source projects and write on Laravel, Linux server, modern JavaScript and more on web development.

Follow

26 Replies to “Laravel Two Factor Authentication with Google Authenticator”

  1. HI,Everthing works except middleware ,there is a message that
    Trying to get property ‘loginSecurity’ of non-object
    :\xampp\htdocs\cpa\app\Support\Google2FAAuthenticator.php:11

  2. Hi MD,
    I was able to successfully follow this tutorial however I am looking to have 2fa setup during registration and have it available when the user is trying to log in, is it possible for you to help me out on how to do that?
    Thanks!

    1. Hello Ahad, You can show 2fa setup during registration & enable on login in some ways. The easiest way:

      Registration: Modify register controller & enable auto-login on registration success. Then redirect user to 2FA setup route.
      Login: Wrap all auth routes with 2fa middleware. After successful login, redirect user to the wrapped route.

      That’s it. I’ve shown the main thing in this article. You can read the article again, can clone the GitHub repo and test. I hope you’ll able to do it soon. 🙂

  3. thanks to you, integration was success.
    but after activating the 2fa for user and after new login, the application dont ask 2fa code from the user.

    whats my mistake? what i lost?
    consider that i wrapped some routes under 2fa middleware and the problem was not solved.
    – user just login normally without asking for googleAuthenticator code.
    – checked the user settings even from db, the 2fa is active for user.

  4. I have the same problem the user activates the 2fa but it does not ask for the code when he tries to log in. What problem could it be?

  5. hi how to wrap login route with 2fa middleware?

    I only have the auth route Auth::routes();
    and other error is Target class [App\Support\Google2FAAuthenticator] does not exist.

    1. Hi GamerTag,

      You don’t need to wrap auth routes. Wrap all restricted routes such as dashboard, settings etc. After login, the user will redirect to the dashboard and will see 2FA input field.

      Google2FAAuthenticator class error: please check step 5 called Create 2FA Middleware.

      Let me know if you still face the issue. Thanks.

  6. The GET method is not supported for this route. Supported methods: POST.
    Route 2faVerify

    if i change from post to get the view 2faVerify it works now.

    in the web.php file it does not work return redirect (URL () -> previous ()); if i change for return view(‘auth/2fa_verify’);

    I have to modify something else?

    putting middleware on home path still doesn’t work

  7. Hello! Have you solved this issue? I have the same problem “The GET method is not supported for this route. Supported methods: POST.”

  8. Ok, once more about the error “The GET method is not supported for this route. Supported methods: POST.”

    It happens after user enters incorrect code and after that enters the correct one: URL()->previous() becomes 2fa/2faVerify, not /test_middleware.

    I solved this by adding hidden input in 2fa_verify.blade.php just below {{ csrf_field() }} :

    get(‘2fa_referrer’) ?? URL()->current() }}”>

    And in web.php changed:

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

    to:

    Route::post(‘/2faVerify’, function () {
    return redirect(request()->get(‘2fa_referrer’));
    })->name(‘2faVerify’)->middleware(‘2fa’);

Leave a Reply