How to add two factor authentication to your laravel application

How to add two factor authentication to your laravel application

Written by Supernova3339 on Apr 24th, 2023 Views Report Post

Two factor authentication is a security measure that adds an extra layer of protection to a user's account or system by requiring two forms of identification. Instead of relying solely on a username and password, it requires the user to provide something they know, such as a password or PIN, and something they possess, such as a physical device like a smartphone or a token.

Securing your Laravel application cannot be overemphasized as it ensures the confidentiality, integrity, and availability of your application data, maintains brand reputation, ensures legal compliance, and protects your application from cyber threats.

Adding two factor authentication is surprisingly simple. We will be:

  • Installing packages
  • Writing controllers
  • Crafting UI
  • Creating migrations
  • Creating models

Package Installation

You'll need the following packages:

  • bacon/bacon-qr-code
  • pragmarx/google2fa-laravel:^2.1
  • pragmarx/google2fa-qrcode:^3.0

You'll need to publish the config file for pragmarx/google2fa-laravel

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

Change the one time password view to auth.2fa_verify

`view` => `auth.2fa_verify`,

Implementation

Implementing two factor authentication is no easy task. Luckily for you, I've broken it down into a few simple steps.

  • Create controllers
  • Create routes
  • Create views
  • Create migrations

To create our LoginSecurityController run the following command:

php artisan make:controller LoginSecurityController

Now that your controller is created, insert the following content:

<?php

namespace App\Http\Controllers;

use Auth;
use Hash;
use Illuminate\Http\Request;
use App\Models\LoginSecurity;
use App\Providers\RouteServiceProvider;

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(
                'CHANGE_THIS_TO_YOUR_APP_NAME',
                $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('/profile/2fa')->with('success',"Secret key is generated.");
    }

    public function register2faSecret(Request $request){
        $user = Auth::user();
        // Initialize 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(RouteServiceProvider::HOME);
    }

    /**
     * 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();
            session()->flash('notification',
            [
                'type' => 'success',
                'message' => '2FA Successfully Enabled'
            ]);
            return redirect('profile/2fa');
        }else{
            session()->flash('notification',
            [
                'type' => 'error',
                'message' => 'Invalid Verification Code'
            ]);
            return redirect('profile/2fa');
        }
    }

    /**
     * Disable 2FA
     */
    public function disable2fa(Request $request){
        if (!(Hash::check($request->get('current-password'), Auth::user()->password))) {
            // The passwords matches
            session()->flash('notification',
            [
                'type' => 'error',
                'message' => 'Invalid Password'
            ]);
            return redirect()->back();


        }

        $validatedData = $request->validate([
            'current-password' => 'required',
        ]);
        $user = Auth::user();
        $user->loginSecurity->google2fa_enable = 0;
        $user->loginSecurity->save();
        return redirect('/profile/2fa');
        session()->flash('notification',
        [
            'type' => 'success',
            'message' => '2FA Disabled'
        ]);
    }

    protected function validateSecret($b32)
    {
        $this->checkForValidCharacters($b32);

        $this->checkGoogleAuthenticatorCompatibility($b32);

        $this->checkIsBigEnough($b32);
    }
}

Routes

Adding routes has been also made easy. Add the below into your routes/web.php file:

use App\Http\Controllers\LoginSecurityController;

/*
|--------------------------------------------------------------------------
| Two Factor Authentication
|--------------------------------------------------------------------------
*/

Route::group(['prefix'=>'2fa'], function(){
    Route::post('/generateSecret',[LoginSecurityController::class, 'generate2faSecret'])->name('generate2faSecret');
    Route::get('/registerSecret',[LoginSecurityController::class, 'register2faSecret'])->name('register2faSecret');
    Route::post('/enable2fa',[LoginSecurityController::class, 'enable2fa'])->name('enable2fa');
    Route::post('/disable2fa',[LoginSecurityController::class, 'disable2fa'])->name('disable2fa');

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

// 2FA Route
Route::get('profile/2fa',[LoginSecurityController::class, 'show2faForm'])->name('2fa');

Views

We will be creating the following views:

  • 2fa_settings
  • 2fa_verify

In your resources/views/auth create 2fa_settings.blade.php. Insert the following content:

    <div class="container mx-auto sm:px-4">
        <div class="flex flex-wrap  md:justify-center">
            <div class="md:w-2/3 pr-4 pl-4">
                <div class="relative flex flex-col min-w-0 rounded break-words border bg-white border-1 border-gray-300">
                    <div class="py-3 px-6 mb-0 bg-gray-200 border-b-1 border-gray-300 text-gray-900"><strong>Two Factor Authentication</strong></div>
                    <div class="flex-auto p-6">

                        @if($data['user']->loginSecurity->google2fa_enable)
                        
                        <div class="p-6 border-l-4 border-green-500 -6 rounded-r-xl bg-green-50">
                            2FA is currently <strong>enabled</strong> on your account.
                        </div><br>

                        @endif

                        <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($data['user']->loginSecurity == null)
                            <form class="form-horizontal" method="POST" action="{{ route('generate2faSecret') }}">
                                {{ csrf_field() }}
                                <div class="mb-4">
                                    <button type="submit" class="inline-block align-middle text-center select-none border font-normal whitespace-no-wrap rounded py-1 px-3 leading-normal no-underline bg-blue-600 text-white hover:bg-blue-600">
                                        Generate Secret Key to Enable 2FA
                                    </button>
                                </div>
                            </form>
                        @elseif(!$data['user']->loginSecurity->google2fa_enable)
                        </br>
                            1. Enter the code <code class="rounded border border-gray-100 bg-pink-100 px-3 py-1 text-xs font-medium text-pink-800">{{ $data['secret'] }}</code> into your authenticator app<br/>
                            {{-- <img src="{{$data['google2fa_url'] }}" alt=""> --}}
                            2. Enter the pin from your authenticator app below &#128071;<br/><br/>
                            <form class="form-horizontal" method="POST" action="{{ route('enable2fa') }}">
                                {{ csrf_field() }}
                                <div class="mb-4{{ $errors->has('verify-code') ? ' has-error' : '' }}">
                                    <label for="secret" class="control-label">Authenticator Code</label>
                                    <input id="secret" type="password" class="block appearance-none w-full py-1 px-2 mb-1 text-base leading-normal bg-white text-gray-800 border border-gray-200 rounded md:w-1/3 pr-4 pl-4" name="secret" required>
                                    @if ($errors->has('verify-code'))
                                        <span class="help-block">
                                        <strong>{{ $errors->first('verify-code') }}</strong>
                                        </span>
                                    @endif
                                </div>
                                <x-primary-button type="submit">
                                    Enable 2FA
                                </x-primary-button>
                            </form>
                        @elseif($data['user']->loginSecurity->google2fa_enable)
                        <br/>
                            <p>If you are looking to disable Two Factor Authentication. Please confirm your password and Click Disable 2FA Button.</p>
                            <br/><form class="form-horizontal" method="POST" action="{{ route('disable2fa') }}">
                                {{ csrf_field() }}
                                <div class="mb-4{{ $errors->has('current-password') ? ' has-error' : '' }}">
                                    <x-input-label for="change-password">Current Password</x-input>
                                        <x-text-input id="current-password" class="mt-1 block w-full" type="password" name="current-password" required />
                                        @if ($errors->has('current-password'))
                                            <span class="help-block">
                                        <strong>{{ $errors->first('current-password') }}</strong>
                                        </span>
                                        @endif
                                </div>
                                <x-primary-button type="submit">
                                    Disable 2FA
                                </x-primary-button>
                            </form>
                        @endif
                    </div>
                </div>
            </div>
        </div>
    </div>

You'll also need to create the verification view, 2fa_verify.blade.php:

    <div class="mb-4 text-sm text-gray-600">
        {{ __('Please confirm access to your account by entering the authentication code provided by your authenticator application.') }}
    </div>

    <!-- Session Status -->
    <x-auth-session-status class="mb-4" :status="session('status')" />

    <form method="POST" action="{{ route('2faVerify') }}">
        @csrf

        <!-- Email Address -->
        <div>
            <x-input-label for="one_time_password" />
            <x-text-input  id="one_time_password" name="one_time_password" class="block mt-1 w-full" type="text" required autofocus />
        </div>

        @if ($errors->any())
            <div class="relative px-3 py-3 mb-4 text-red-500">
                <ul>
                    @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                    @endforeach
                </ul>
            </div>
        @endif

        <div class="flex items-center justify-end mt-4">
            <x-primary-button>
                {{ __('Authenticate') }}
            </x-primary-button>
        </div>
    </form>

Migrations

We will be creating one migration, our login_securities_table. Run the following command in your terminal to create the migration:

php artisan make:migration create_login_securities_table

Insert the following content into create_login_securities_table:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        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();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('login_securities');
    }
};

Models

We will be creating our LoginSecurity model under app/Http/Models. Insert the following code to create the model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class LoginSecurity extends Model
{
    use HasFactory;

    protected $fillable = [
        'user_id'
    ];
    
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

In this tutorial, we:

  • Installed packages
  • Created views, routes, migrations, models, and controllers
  • Learned how to use pragmarx/google2fa-laravel

In conclusion, implementing two factor authentication in your Laravel application provides an extra layer of security that protects your application from unauthorized access and breaches. There are multiple packages available for implementing two factor authentication in Laravel, which provide customizable options for views, controllers and routes.

Using packages such as pragmarx/google2fa-laravel, pragmarx/google2fa-qrcode, and bacon/bacon-qr-code, you can easily add two factor authentication to your Laravel application. With these packages, you can create customizable views for the two factor authentication screen, set up appropriate routes, and implement controllers that add an additonal layer of security to your applications authentication flow.

By securing your Laravel application with two factor authentication, you can prevent unauthorized access, protect sensitive data, maintain legal compliance and protect your brand reputation. So, it is highly recommended to implement two factor authentication in your Laravel application for increased security and peace of mind.

Comments (1)