Though default laravel jetsream laravel authentication provides two factor(2fa) athentication but in this example, we are not gonna use this deafult features. In this tutorial, we will create laravel two factor authentication email with our own custom code. Hope this laravel two factor authentication with email tutorial will solve your problem.
I will share step by step of laravel 2fa with email authentication. So you will learn about two factor authentication twilio laravel. We will use mail system for sending otp code. So you will learn 2 factor auth laravel email system. So let's see how we can do the following things for laravel two factor authentication email.
Login page:
Code verification page:
Email with code:
Step 1 : Install Laravel
First of all, we need to get a fresh Laravel 9 version application using the bellow command, So open your terminal OR command prompt and run the bellow command to start email based 2fa login system laravel:
composer create-project laravel/laravel example-app
Read also: Laravel 9 SMS Based Two Factor (2FA) Authentication
Step 2: Create Migration
We have to verify code before login. So we need a new table like "user_codes". So create it by the following command:
php artisan make:migration create_user_codes
After this command you will find one file in the following path "database/migrations" and you have to put the bellow code in your migration file to create the user_codes table.
<?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 you have to run this migration by following the command:
php artisan migrate
Recommended: Why And When To Use Interface In Laravel Application
Step 4: Create Models
Here, we need to update the user model and create a new model called UserCode. so let's update the code:
app/Models/User.php
<?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 Mail;
use App\Mail\SendCodeMail;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'name',
'email',
'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',
];
/**
* Write code on Method
*
* @return response()
*/
public function generateCode()
{
$code = rand(1000, 9999);
UserCode::updateOrCreate(
[ 'user_id' => auth()->user()->id ],
[ 'code' => $code ]
);
try {
$details = [
'title' => 'Mail from laravelia.com',
'code' => $code
];
Mail::to(auth()->user()->email)->send(new SendCodeMail($details));
} catch (Exception $e) {
info("Error: ". $e->getMessage());
}
}
}
Now update the "UserCode" model:
app/Models/UserCode.php
<?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
Here, we will use laravel ui
package and create auth scaffold with the bootstrap framework. let's follow bellow command:
composer require laravel/ui
Now create a simple bootstrap auth system:
php artisan ui bootstrap --auth
Step 5: Create Middleware
In this step, I will create new middleware to check whether the user has a code to login with 2fa or not. so let's create new middleware with the following command and code:
php artisan make:middleware Check2FA
Now update the middleware like:
<?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);
}
}
Now register the middleware like:
app/Http/Kernel.php
<?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 6: Create Route
We need to create register routes as below:
routes/web.php
<?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 7: Create Controllers
Here, we need to update TwoFAController and LoginController controller files. Update it with the below code:
app/Http/Controllers/Auth/LoginController.php
<?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');
}
}
Now update the two factor controller with the below code:
app/Http/Controllers/TwoFAController.php
<?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 email.');
}
}
Read also: How To Implement Remember Me With Expiration Time In Laravel?
Step 8: Create Mail
In this step, we need to create mail for sending otp code before login. So create it by the below command:
php artisan make:mail SendCodeMail
app/Mail/SendCodeMail.php
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class SendCodeMail extends Mailable
{
use Queueable, SerializesModels;
public $details;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($details)
{
$this->details = $details;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->subject('Mail from laravelia.com')
->view('emails.code');
}
}
Update the mail configuration with your credentials.
.env
MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=mygoogle@gmail.com
MAIL_PASSWORD=rrnnucvnqlbsl
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=mygoogle@gmail.com
MAIL_FROM_NAME="${APP_NAME}"
Step 9: Create Blade File
Here, we will create a code blade file and create a new 2fa blade file. so let's update the following code here.
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 email : {{ substr(auth()->user()->email, 0, 5) . '******' . substr(auth()->user()->email, -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
We need another mail blade for code verification. So create it like:
resources/views/emails/code.blade.php
<!DOCTYPE html>
<html>
<head>
<title>laravelia.com</title>
</head>
<body>
<h1>{{ $details['title'] }}</h1>
<p>Your code is : {{ $details['code'] }}</p>
<p>Thank you</p>
</body>
</html>
Ok, now we are ready to go and test our custom two-factor authentication system in laravel with email. So let's run the project using this command:
php artisan serve
Now you can test our application by visiting the below URL:
URL
Read also: Laravel Passwordless Login | Login With Username In Laravel
Conclusion
Now we know laravel otp email based two factor login system. Hope this laravel two factor authentication with email tutorial will help you to create your custom 2fa login system.