In this tutorial you will learn about the Laravel 8 Two Factor Authentication with SMS and its application with practical example.
In this Laravel 8 Two Factor Authentication with SMS tutorial I will show you how to implement Two Factor Authentication with SMS in laravel 8. In this tutorial you will learn to authenticate user using SMS Two Factor Authentication in laravel. In this article I will share example to implement two factor authentication in laravel application. We will be using twilio api to send sms for two factor authentication. In two factor authentication using sms we will be sending sms to validate user account in laravel.
Laravel 8 Two Factor Authentication with SMS
In this step by step two factor authentication tutorial I will demonstrate you with example on how to add sms two factor authentication in laravel. Please follow the instruction given below:
Step 1 : Install Laravel
First of all we need to create a fresh laravel project, download and install Laravel 8 using the below command
1 |
composer create-project laravel/laravel example-app |
Step 2: Install Twilio Configuration
First of all you need to create account on twilio.com and add phone number. Then you will be provided account SID, Token and Number. Now, you need to add account SID, Token and Number in .env file:
.env
1 2 3 |
TWILIO_SID=XXXXXXXXXXXXXXXXX TWILIO_TOKEN=XXXXXXXXXXXXX TWILIO_FROM=+XXXXXXXXXXX |
Now you need to install twilio/sdk composer package to use twilio api. Use the following command to install twilio/sdk.
1 |
composer require twilio/sdk |
Step 3: Create Migration for table
Now, in this step we will create migration file. Please run the following command:
1 |
php artisan make:migration create_user_codes |
Once above command is executed there will be a migration file created inside database/migrations/ directory, just open migration file and update the function up() method as following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; 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, run the migration to create database table using following artisan command:
1 |
php artisan migrate |
Step 4: Create and Update Models
Next we will create a model file.
app/Models/User.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
<?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 Exception; use Twilio\Rest\Client; 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
a<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class UserCode extends Model { use HasFactory; public $table = "user_codes"; protected $fillable = [ 'user_id', 'code', ]; } |
Step 5: Create Auth Scaffold
Use the following command to generate laravel auth scaffolding:
1 |
composer require laravel/ui |
Lets create auth scaffold:
1 |
php artisan ui bootstrap --auth |
Step 6: Create Middleware
Now we will create a custom middleware to implement two factor authentication.
1 |
php artisan make:middleware Check2FA |
app/Http/Middleware/Check2FA.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Session; class Check2FA { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?php namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { ... ... ... protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, '2fa' => \App\Http\Middleware\Check2FA::class, ]; } |
Step 7: Create Route
After this, we need to define routes in “routes/web.php” file. Lets open “routes/web.php” file and add the following routes in it.
routes/web.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<?php use Illuminate\Support\Facades\Route; /* |-------------------------------------------------------------------------- | 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', [App\Http\Controllers\HomeController::class, 'index'])->name('home')->middleware('2fa'); Route::get('2fa', [App\Http\Controllers\TwoFAController::class, 'index'])->name('2fa.index'); Route::post('2fa', [App\Http\Controllers\TwoFAController::class, 'store'])->name('2fa.post'); Route::get('2fa/reset', [App\Http\Controllers\TwoFAController::class, 'resend'])->name('2fa.resend'); |
Step 8: Create and Update Controllers
Now, lets open RegisterController and LoginController and put the following code in it:
app/Http/Controllers/Auth/RegisterController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
<?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; 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 \Illuminate\Contracts\Validation\Validator */ 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 \App\Models\User */ 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use App\Providers\RouteServiceProvider; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; use Auth; use App\Models\UserCode; 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'); } } |
Let’s create TwoFAController controller and put the following code in it.
app/Http/Controllers/TwoFAController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Session; use App\Models\UserCode; 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
Finally we will update default register blade file and create new 2fa blade file as following:
resources/views/auth/register.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
@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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
@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 |
Run Development Server
Now we are ready to run our example so lets start the development server using following artisan command –
1 |
php artisan serve |
Now, open the following URL in browser to see the output –
1 |
localhost:8000/ |