Laravel provides varienty of authentication feature out-of-the-box. Additionally you can also customize authentication flow as per your requirement.
In this tutorial article, we will discuss on how to create two factor authentication in Laravel 8. We will use Mobile OTP authentication with SMS. In this tutorial, we will use Twilio service to send SMS to international mobile number.
We will go step by step through tutorial from the scartch. The tutorial covers following steps:
- Step 1: Create new Laravel application
- Step 2: Install and configure Twilio library
- Step 3: Configure database in .env file
- Step 4: Create and run migration file
- Step 5: Create UserCode model and update User model
- Step 6: Create authentication scaffold
- Step 7: Create middleware class
- Step 8: Register authentication routes
- Step 9: Create and update controller class
- Step 10: Create and update blade files
- Step 11: Run and testing application
Let's get started with laravel two factor authentication tutorial.
Step 1: Create new Laravel application
First of all, we need to create fresh Laravel application using Composer command. So open your terminal OR command prompt and run below command:
composer create-project laravel/laravel authentication --prefer-dist
Then change Terminal working directory to project root.
cd authentication
Step 2: Install and configure Twilio library
In the second step, we will install twilio/sdk library which provides easy way to send SMS in Laravel application.
composer require twilio/sdk
While the library is installing, let's create Twilio account and get account SID, token and number. After creating Twilio account, add Twilio credentials at .env file at the root directory.
TWILIO_SID=twilio_sid
TWILIO_TOKEN=twilio_token
TWILIO_FROM=number_here
Step 3: Configure database in .env file
Now we will need to configure database connection. In the .env file, change below database credentials with your MySQL.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=jwt
DB_USERNAME=root
DB_PASSWORD=secret
Step 4: Create and run migration file
In the forth step, we will create user_codes
migration file using below Artisan command.
php artisan make:migration create_user_codes_table
The command will create migration class at database/migrations
directory.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserCodesTable 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();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_codes');
}
}
In the users table migration file, add phone
field.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('phone')->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
After these changes done, run the migrate command to generate tables into database.
php artisan migrate
Step 5: Create UserCode model and update User model
We will need to create UserCode
model using below command.
php artisan make:model UserCode
Now open the model class at app/Models/UserCode.php
file and add $fillable
property.
<?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',
];
}
In the User
model, add following class method to generate and send sms.
<?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 Twilio\Rest\Client;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'name',
'email',
'phone',
'password',
];
/**
* 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',
];
/**
* generate OTP and send sms
*
* @return response()
*/
public function generateCode()
{
$code = rand(100000, 999999);
UserCode::updateOrCreate([
'user_id' => auth()->user()->id,
'code' => $code
]);
$receiverNumber = auth()->user()->phone;
$message = "Your Login OTP code is ". $code;
try {
$account_sid = getenv("TWILIO_SID");
$auth_token = getenv("TWILIO_TOKEN");
$number = getenv("TWILIO_FROM");
$client = new Client($account_sid, $auth_token);
$client->messages->create($receiverNumber, [
'from' => $number,
'body' => $message]);
} catch (\Exception $e) {
//
}
}
}
Step 6: Create authentication scaffold
Now, we will create Laravel default authentication scaffold using composer command.
composer require laravel/ui
And render authentication views using following command.
php artisan ui bootstrap --auth
Run the following npm command to compile the assets.
npm install && npm run dev
Step 7: Create middleware class
In this step, we will create a middleware class which will check if user has two factor authentication enabled or not. Run the below Artisan command to generate TwoFactorAuth
middleware class at app/Http/Middleware
directory
php artisan make:middleware TwoFactorAuth
Now open app/Http/Middleware/TwoFactorAuth.php
and add below code into handle()
method.
<?php
namespace App\Http\Middleware;
use Closure;
use Session;
use Illuminate\Http\Request;
class TwoFactorAuth
{
/**
* 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);
}
}
We will also need to register new middleware at app/Http/Kernel.php
$routeMiddleware array.
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
....
'2fa' => \App\Http\Middleware\TwoFactorAuth::class,
];
}
Step 8: Register authentication routes
In this step, we will need to register authentication routes at routes/web.php
file.
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\HomeController;
use App\Http\Controllers\TwoFactorAuthController;
Auth::routes();
Route::get('/home', [HomeController::class, 'index'])->name('home');
Route::get('two-factor-auth', [TwoFactorAuthController::class, 'index'])->name('2fa.index');
Route::post('two-factor-auth', [TwoFactorAuthController::class, 'store'])->name('2fa.store');
Route::get('two-factor-auth/resent', [TwoFactorAuthController::class, 'resend'])->name('2fa.resend');
Step 9: Create and update controller class
We have already register routes and controller methods. We are adding two factor authentication feature into current authentication flow. So we need to modify RegisterController and LoginController. For 2fa routes, we will create a seperate controller.
First open app/Http/Controllers/Auth/RegisterController.php
file and add phone field into user generate array.
<?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
{
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'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'phone' => ['required', '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'],
'email' => $data['email'],
'phone' => $data['phone'],
'password' => Hash::make($data['password']),
]);
}
}
In the app/Http/Controllers/Auth/LoginController.php
file, modify login method.
<?php
namespace App\Http\Controllers\Auth;
use Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
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');
}
/**
* process login
*
* @return response()
*/
public function login(Request $request)
{
$validated = $request->validate([
'email' => 'required',
'password' => 'required',
]);
if (Auth::attempt($validated)) {
auth()->user()->generateCode();
return redirect()->route('2fa.index');
}
return redirect()
->route('login')
->with('error', 'You have entered invalid credentials');
}
}
Now create TwoFactorAuthController controller class using following command.
php artisan make:controller TwoFactorAuthController
Now open controller file and add following class methods.
<?php
namespace App\Http\Controllers;
use App\Models\UserCode;
use Illuminate\Http\Request;
class TwoFactorAuthController extends Controller
{
/**
* index method for 2fa
*
* @return response()
*/
public function index()
{
return view('2fa');
}
/**
* validate sms
*
* @return response()
*/
public function store(Request $request)
{
$validated = $request->validate([
'code' => 'required',
]);
$exists = UserCode::where('user_id', auth()->user()->id)
->where('code', $validated['code'])
->where('updated_at', '>=', now()->subMinutes(5))
->exists();
if ($exists) {
\Session::put('tfa', auth()->user()->id);
return redirect()->route('home');
}
return redirect()
->back()
->with('error', 'You entered wrong OTP code.');
}
/**
* resend otp code
*
* @return response()
*/
public function resend()
{
auth()->user()->generateCode();
return back()
->with('success', 'We have resent OTP on your mobile number.');
}
}
Step 10: Create and update blade files
This is the last step for coding. In this step, we will update default register code and add new blade views for OTP input.
First start updating resources/views/auth/register.blade.php
file. Add phone field into register view.
@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="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="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="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
We need to create otp input view file. Create 2fa.blade.php
file and add below HTML code into it.
@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.store') }}">
@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
Step 11: Run and testing application
That's it. We have created the application. Now we are going to test the application. First run the Laravel server using below command into Terminal.
php artisan serve
Run the url http://localhost:8000/register
into your favourite browser. Try to register and login new user.
Conclusion
Finally the tutorial is over. We have learned how to create two factor authentication into default Laravel authentication scaffolding.
I hope the tutorial helped you on your development. Thank you for giving time in reading article.