In today's digital world, security is a paramount concern. Protecting user accounts from unauthorized access is a top priority for web developers and businesses. One effective method to enhance security is by implementing Two-Factor Authentication (2FA).
Laravel, one of the most popular PHP web application frameworks, has made it easier than ever to integrate 2FA into your applications. In this article, we will explore how to implement 2FA authentication in Laravel 10.
What is Two-Factor Authentication (2FA)?
Two-Factor Authentication (2FA) is a security process in which a user provides two different authentication factors to verify their identity. These factors typically fall into one of the following categories:
- Something you know: This is usually a password or PIN.
- Something you have: This could be a mobile device or a hardware token.
- Something you are: This involves biometrics like fingerprints or facial recognition.
Implementing 2FA adds an extra layer of security because even if an attacker knows a user's password, they would still need access to the second factor to gain entry.
Google Authentication Apps:
Google Authenticator is a popular app that generates time-based one-time passwords (TOTP) for 2FA.
To use the two-factor authentication, the user will have to install a Google Authenticator compatible app. Here are a few:
Let's dive deeper into how to integrate Google 2FA into your Laravel application.
Before we dive into implementing 2FA, let's create a new Laravel 10 project if you haven't already. You can do this using Composer.
composer create-project laravel/laravel laravel-2fa
After we install Laravel UI using the following command.
composer require laravel/ui
Then, create Laravel default authentication using the following command.
Using Bootstrap:
php artisan ui bootstrap --auth
Add a new column to your users table to store the user's secret key for Google 2FA. You can use Laravel's migration and schema builder for this.
php artisan make:migration add_google2fa_secret_to_users_table
In the generated migration file, add the following code.
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('google2fa_secret')->nullable();
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('google2fa_secret');
});
}
Then, run the migration.
php artisan migrate
After update app/Models/User.php file.
We need to create a getter and setter for the 'google2fa_secret' column for the encrypt and decrypt column.
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Database\Eloquent\Casts\Attribute;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
'google2fa_secret'
];
/**
* The attributes that should be hidden for serialization.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
'google2fa_secret'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
/**
* Interact with the user's first name.
*
* @param string $value
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
protected function google2faSecret(): Attribute
{
return new Attribute(
get: fn ($value) => decrypt($value),
set: fn ($value) => encrypt($value),
);
}
}
To implement Google 2FA in Laravel, you'll need to install the necessary packages. In this example, we'll use the pragmarx/google2fa
package.
composer require pragmarx/google2fa-laravel
composer require bacon/bacon-qr-code
Optional:
....
'providers' => [
....
PragmaRX\Google2FALaravel\ServiceProvider::class,
],
'aliases' => [
....
'Google2FA' => PragmaRX\Google2FALaravel\Facade::class,
],
....
To enforce Google 2FA for specific routes or controllers, you can use Laravel's middleware. Create a custom middleware that checks if the user has enabled Google 2FA, and if not, redirect them to set it up.
app/Http/Kernel.php
protected $routeMiddleware = [
...
'2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
];
We will update the RegisterController and register() method and include the Request class outside the controller class.
Instead of
use RegistersUsers;
using this:
use RegistersUsers {
register as registration;
}
app/Http/Controllers/Auth/RegisterController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\Request;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers {
register as registration;
}
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:6', 'confirmed'],
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\Models\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'google2fa_secret' => $data['google2fa_secret'],
]);
}
/**
* Write code on Method
*
* @return response()
*/
public function register(Request $request)
{
$this->validator($request->all())->validate();
$google2fa = app('pragmarx.google2fa');
$registration_data = $request->all();
$registration_data["google2fa_secret"] = $google2fa->generateSecretKey();
$request->session()->flash('registration_data', $registration_data);
$QR_Image = $google2fa->getQRCodeInline(
config('app.name'),
$registration_data['email'],
$registration_data['google2fa_secret']
);
return view('google2fa.register', ['QR_Image' => $QR_Image, 'secret' => $registration_data['google2fa_secret']]);
}
/**
* Write code on Method
*
* @return response()
*/
public function completeRegistration(Request $request)
{
$request->merge(session('registration_data'));
return $this->registration($request);
}
}
In this step register() method already redirects to google2fa/register.blade.php. This means we will create google2fa folder and a register.blade.php file.
resources/views/google2fa/register.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-12 mt-4">
<div class="card card-default">
<h4 class="card-heading text-center mt-4">Set up Google Authenticator</h4>
<div class="card-body" style="text-align: center;">
<p>Set up your two factor authentication by scanning the QR Code below. Alternatively, you can use the code <strong>{{ $secret }}</strong></p>
<div>
<img src="{{ $QR_Image }}">
</div>
<p>You must set up your Google Authenticator app before continuing. You will be unable to login otherwise</p>
<div>
<a href="{{ route('complete.registration') }}" class="btn btn-primary">Complete Registration</a>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
Now, we will add a route into the web.php file.
routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\HomeController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
// 2fa middleware
Route::middleware(['2fa'])->group(function () {
// HomeController
Route::get('/home', [HomeController::class, 'index'])->name('home');
Route::post('/2fa', function () {
return redirect(route('home'));
})->name('2fa');
});
Route::get('/complete-registration', [RegisterController::class, 'completeRegistration'])->name('complete.registration');
After scanning the QR code or inputting a secret on the Google Authenticator app, it automatically generates an OTP on the Authenticator App.
resources/views/google2fa/index.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center align-items-center " style="height: 70vh;S">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading font-weight-bold">Register</div>
<hr>
@if($errors->any())
<div class="col-md-12">
<div class="alert alert-danger">
<strong>{{$errors->first()}}</strong>
</div>
</div>
@endif
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('2fa') }}">
{{ csrf_field() }}
<div class="form-group">
<p>Please enter the <strong>OTP</strong> generated on your Authenticator App. <br> Ensure you submit the current one because it refreshes every 30 seconds.</p>
<label for="one_time_password" class="col-md-4 control-label">One Time Password</label>
<div class="col-md-6">
<input id="one_time_password" type="number" class="form-control" name="one_time_password" required autofocus>
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4 mt-3">
<button type="submit" class="btn btn-primary">
Login
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
Run the following command to start the server.
php artisan serve
Integrating Google 2FA into your Laravel application can significantly enhance security by adding an extra layer of authentication. By following the steps outlined above, you can implement Google Authenticator-based 2FA for your users and enhance the overall security of your Laravel application.
You might also like:
- Read Also: Difference between Single Quotes and Double Quotes in PHP
- Read Also: Laravel 10 Google Line Chart Example
- Read Also: Laravel 10 One To Many Polymorphic Relationship
- Read Also: How to Send Email with PDF Attachment in Laravel 10