How to Add Two Factor Authentication with SMS in Laravel?

Hello,

Here, i will show you how to works laravel-two-factor authentication sms. step by step explain laravel two factor authentication with sms. if you want to see example of laravel 2fa with sms authentication then you are a right place. you will learn two factor authentication twilio laravel. Let’s get started with laravel two factor authentication sms.

you can use this example with laravel 6, laravel 7 and laravel 8 version.

In this example, i will give you step by step all info how to add two factor authentication with twilio sms in laravel 8. we will use twilio service for sending mobile text message. you can follow bellow step and make it done.

Register Page:

How to Add Two Factor Authentication with SMS in Laravel?

Verify Code Page:

How to Add Two Factor Authentication with SMS in Laravel?

Step 1 : Install Laravel

first of all we need to get fresh Laravel version application using bellow command, So open your terminal OR command prompt and run bellow command:

composer create-project laravel/laravel example-app

Step 2: Install Twilio Configuration

First you need to create and add phone number. then you can easily get account SID, Token and Number.

Create Account from here: www.twilio.com.

Next add Twilio Phone Number

Next you can get account SID, Token and Number and add on .env file as like bellow:

.env

TWILIO_SID=XXXXXXXXXXXXXXXXX

TWILIO_TOKEN=XXXXXXXXXXXXX

TWILIO_FROM=+XXXXXXXXXXX

next, we need to install twilio/sdk composer package to use twilio api. so let’s run bellow command:

composer require twilio/sdk

Step 3: Create Migration for table

here, we need to create “user_codes” table and need to add phone field on users table. so let’s create migration as bellow:

php artisan make:migration create_user_codes

After this command you will find one file in following path “database/migrations” and you have to put bellow code in your migration file for create user_codes table.

<?php

use IlluminateDatabaseMigrationsMigration;

use IlluminateDatabaseSchemaBlueprint;

use IlluminateSupportFacadesSchema;

class CreateUserCodes extends Migration

{

/**

* Run the migrations.

*

* @return void

*/

public function up()

{

Schema::create('user_codes', function (Blueprint $table) {

$table->id();

$table->integer('user_id');

$table->string('code');

$table->timestamps();

});

Schema::table('users', function (Blueprint $table) {

$table->string('phone')->nullable();

});

}

/**

* Reverse the migrations.

*

* @return void

*/

public function down()

{

Schema::dropIfExists('user_codes');

}

}

Now you have to run this migration by following command:

php artisan migrate

Step 4: Create and Update Models

Here, we need to update user model and create new model call UserCode. so let’s update code:

app/Models/User.php

<?php

namespace AppModels;

use IlluminateContractsAuthMustVerifyEmail;

use IlluminateDatabaseEloquentFactoriesHasFactory;

use IlluminateFoundationAuthUser as Authenticatable;

use IlluminateNotificationsNotifiable;

use LaravelSanctumHasApiTokens;

use Exception;

use TwilioRestClient;

class User extends Authenticatable

{

use HasApiTokens, HasFactory, Notifiable;

/**

* The attributes that are mass assignable.

*

* @var string[]

*/

protected $fillable = [

'name',

'email',

'password',

'phone'

];

/**

* The attributes that should be hidden for serialization.

*

* @var array

*/

protected $hidden = [

'password',

'remember_token',

];

/**

* The attributes that should be cast.

*

* @var array

*/

protected $casts = [

'email_verified_at' => 'datetime',

];

/**

* Write code on Method

*

* @return response()

*/

public function generateCode()

{

$code = rand(1000, 9999);

UserCode::updateOrCreate(

[ 'user_id' => auth()->user()->id ],

[ 'code' => $code ]

);

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

$message = "2FA login code is ". $code;

try {

$account_sid = getenv("TWILIO_SID");

$auth_token = getenv("TWILIO_TOKEN");

$twilio_number = getenv("TWILIO_FROM");

$client = new Client($account_sid, $auth_token);

$client->messages->create($receiverNumber, [

'from' => $twilio_number,

'body' => $message]);

} catch (Exception $e) {

info("Error: ". $e->getMessage());

}

}

}

app/Models/UserCode.php

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;

use IlluminateDatabaseEloquentModel;

class UserCode extends Model

{

use HasFactory;

public $table = "user_codes";

protected $fillable = [

'user_id',

'code',

];

}

Step 5: Create Auth Scaffold

Here, we will use laravel ui package and create auth scaffold with bootstrap framework. let’s follow bellow command:

composer require laravel/ui

now let’s run command for creating auth scaffold:

php artisan ui bootstrap --auth

Step 6: Create Middleware

In this step, we will create new middleware for check user has 2fa or not. so let’s create new middleware with following command and code:

php artisan make:middleware Check2FA

app/Http/Middleware/Check2FA.php

<?php

namespace AppHttpMiddleware;

use Closure;

use IlluminateHttpRequest;

use Session;

class Check2FA

{

/**

* Handle an incoming request.

*

* @param IlluminateHttpRequest $request

* @param Closure $next

* @return mixed

*/

public function handle(Request $request, Closure $next)

{

if (!Session::has('user_2fa')) {

return redirect()->route('2fa.index');

}

return $next($request);

}

}

app/Http/Kernel.php

<?php

namespace AppHttp;

use IlluminateFoundationHttpKernel as HttpKernel;

class Kernel extends HttpKernel

{

...

...

...

protected $routeMiddleware = [

'auth' => AppHttpMiddlewareAuthenticate::class,

'auth.basic' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class,

'cache.headers' => IlluminateHttpMiddlewareSetCacheHeaders::class,

'can' => IlluminateAuthMiddlewareAuthorize::class,

'guest' => AppHttpMiddlewareRedirectIfAuthenticated::class,

'password.confirm' => IlluminateAuthMiddlewareRequirePassword::class,

'signed' => IlluminateRoutingMiddlewareValidateSignature::class,

'throttle' => IlluminateRoutingMiddlewareThrottleRequests::class,

'verified' => IlluminateAuthMiddlewareEnsureEmailIsVerified::class,

'2fa' => AppHttpMiddlewareCheck2FA::class,

];

}

Step 7: Create Route

We need to create register routes as bellow:

routes/web.php

<?php

use IlluminateSupportFacadesRoute;

/*

|--------------------------------------------------------------------------

| 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();

Route::get('/home', [AppHttpControllersHomeController::class, 'index'])->name('home')->middleware('2fa');

Route::get('2fa', [AppHttpControllersTwoFAController::class, 'index'])->name('2fa.index');

Route::post('2fa', [AppHttpControllersTwoFAController::class, 'store'])->name('2fa.post');

Route::get('2fa/reset', [AppHttpControllersTwoFAController::class, 'resend'])->name('2fa.resend');

Step 8: Create and Update Controllers

Here, we need to update RegisterController and LoginController controller file.

Also we will create TwoFAController new controller file and write code there. so let’s create these files:

app/Http/Controllers/Auth/RegisterController.php

<?php

namespace AppHttpControllersAuth;

use AppHttpControllersController;

use AppProvidersRouteServiceProvider;

use AppModelsUser;

use IlluminateFoundationAuthRegistersUsers;

use IlluminateSupportFacadesHash;

use IlluminateSupportFacadesValidator;

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;

/**

* Where to redirect users after registration.

*

* @var string

*/

protected $redirectTo = RouteServiceProvider::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 IlluminateContractsValidationValidator

*/

protected function validator(array $data)

{

return Validator::make($data, [

'name' => ['required', 'string', 'max:255'],

'phone' => ['required'],

'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],

'password' => ['required', 'string', 'min:8', 'confirmed'],

]);

}

/**

* Create a new user instance after a valid registration.

*

* @param array $data

* @return AppModelsUser

*/

protected function create(array $data)

{

return User::create([

'name' => $data['name'],

'phone' => $data['phone'],

'email' => $data['email'],

'password' => Hash::make($data['password']),

]);

}

}

app/Http/Controllers/Auth/LoginController.php

<?php

namespace AppHttpControllersAuth;

use AppHttpControllersController;

use AppProvidersRouteServiceProvider;

use IlluminateFoundationAuthAuthenticatesUsers;

use IlluminateHttpRequest;

use Auth;

use AppModelsUserCode;

class LoginController extends Controller

{

/*

|--------------------------------------------------------------------------

| Login Controller

|--------------------------------------------------------------------------

|

| This controller handles authenticating users for the application and

| redirecting them to your home screen. The controller uses a trait

| to conveniently provide its functionality to your applications.

|

*/

use AuthenticatesUsers;

/**

* Where to redirect users after login.

*

* @var string

*/

protected $redirectTo = RouteServiceProvider::HOME;

/**

* Create a new controller instance.

*

* @return void

*/

public function __construct()

{

$this->middleware('guest')->except('logout');

}

/**

* Write code on Method

*

* @return response()

*/

public function login(Request $request)

{

$request->validate([

'email' => 'required',

'password' => 'required',

]);

$credentials = $request->only('email', 'password');

if (Auth::attempt($credentials)) {

auth()->user()->generateCode();

return redirect()->route('2fa.index');

}

return redirect("login")->withSuccess('Oppes! You have entered invalid credentials');

}

}

app/Http/Controllers/TwoFAController.php

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;

use Session;

use AppModelsUserCode;

class TwoFAController extends Controller

{

/**

* Write code on Method

*

* @return response()

*/

public function index()

{

return view('2fa');

}

/**

* Write code on Method

*

* @return response()

*/

public function store(Request $request)

{

$request->validate([

'code'=>'required',

]);

$find = UserCode::where('user_id', auth()->user()->id)

->where('code', $request->code)

->where('updated_at', '>=', now()->subMinutes(2))

->first();

if (!is_null($find)) {

Session::put('user_2fa', auth()->user()->id);

return redirect()->route('home');

}

return back()->with('error', 'You entered wrong code.');

}

/**

* Write code on Method

*

* @return response()

*/

public function resend()

{

auth()->user()->generateCode();

return back()->with('success', 'We sent you code on your mobile number.');

}

}

Step 9: Create and Update Blade File

Here, we will update register default blade file and create new 2fa blade file. so let’s update following code here.

resources/views/auth/register.blade.php

@extends('layouts.app')

@section('content')

<div class="container">

<div class="row justify-content-center">

<div class="col-md-8">

<div class="card">

<div class="card-header">{{ __('Register') }}</div>

<div class="card-body">

<form method="POST" action="{{ route('register') }}">

@csrf

<div class="form-group row">

<label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>

<div class="col-md-6">

<input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>

@error('name')

<span class="invalid-feedback" role="alert">

<strong>{{ $message }}</strong>

</span>

@enderror

</div>

</div>

<div class="form-group row">

<label for="name" class="col-md-4 col-form-label text-md-right">Phone</label>

<div class="col-md-6">

<input id="phone" type="text" class="form-control @error('phone') is-invalid @enderror" name="phone" value="{{ old('phone') }}" required autocomplete="phone" autofocus>

@error('phone')

<span class="invalid-feedback" role="alert">

<strong>{{ $message }}</strong>

</span>

@enderror

</div>

</div>

<div class="form-group row">

<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>

<div class="col-md-6">

<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">

@error('email')

<span class="invalid-feedback" role="alert">

<strong>{{ $message }}</strong>

</span>

@enderror

</div>

</div>

<div class="form-group row">

<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>

<div class="col-md-6">

<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">

@error('password')

<span class="invalid-feedback" role="alert">

<strong>{{ $message }}</strong>

</span>

@enderror

</div>

</div>

<div class="form-group row">

<label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>

<div class="col-md-6">

<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">

</div>

</div>

<div class="form-group row mb-0">

<div class="col-md-6 offset-md-4">

<button type="submit" class="btn btn-primary">

{{ __('Register') }}

</button>

</div>

</div>

</form>

</div>

</div>

</div>

</div>

</div>

@endsection

resources/views/2fa.blade.php

@extends('layouts.app')

@section('content')

<div class="container">

<div class="row justify-content-center">

<div class="col-md-8">

<div class="card">

<div class="card-header">2FA Verification</div>

<div class="card-body">

<form method="POST" action="{{ route('2fa.post') }}">

@csrf

<p class="text-center">We sent code to your phone : {{ substr(auth()->user()->phone, 0, 5) . '******' . substr(auth()->user()->phone, -2) }}</p>

@if ($message = Session::get('success'))

<div class="row">

<div class="col-md-12">

<div class="alert alert-success alert-block">

<button type="button" class="close" data-dismiss="alert">×</button>

<strong>{{ $message }}</strong>

</div>

</div>

</div>

@endif

@if ($message = Session::get('error'))

<div class="row">

<div class="col-md-12">

<div class="alert alert-danger alert-block">

<button type="button" class="close" data-dismiss="alert">×</button>

<strong>{{ $message }}</strong>

</div>

</div>

</div>

@endif

<div class="form-group row">

<label for="code" class="col-md-4 col-form-label text-md-right">Code</label>

<div class="col-md-6">

<input id="code" type="number" class="form-control @error('code') is-invalid @enderror" name="code" value="{{ old('code') }}" required autocomplete="code" autofocus>

@error('code')

<span class="invalid-feedback" role="alert">

<strong>{{ $message }}</strong>

</span>

@enderror

</div>

</div>

<div class="form-group row mb-0">

<div class="col-md-8 offset-md-4">

<a class="btn btn-link" href="{{ route('2fa.resend') }}">Resend Code?</a>

</div>

</div>

<div class="form-group row mb-0">

<div class="col-md-8 offset-md-4">

<button type="submit" class="btn btn-primary">

Submit

</button>

</div>

</div>

</form>

</div>

</div>

</div>

</div>

</div>

@endsection

Ok, now we are ready to run.

So let’s run project using this command:

php artisan serve

open bellow UR:

localhost:8000/

Now you can simply run and check…

I hope it can help you….

Leave a Comment