Create Custom Two Factor Authentication System In Laravel

Hello artisan,

In this brand new Laravel two factor authentication tutorial, I am going to show you from step by step Laravel two factor authentication using email tutorial. I am going to show you this two factor authentication using mail. To create this Laravel two factor authentication email tutorial, you have to just follow some steps. 

You know that two factor authentication gives us stronger security for our account. This two factor authentication gives us an extra layer of security. Most users only have one layer – their password – to protect their accounts.

In this example, I will use email to verify our sending code before authenticating a user. Just follow the following steps to complete laravel 2fa with email authentication tutorial. We can create this Laravel two step or two factor authentication using OTP (One Time Password), But in this example, I will use email to create this 2fa system in Laravel.

 

Step 1 : Download Laravel

In this first step, we need to get a fresh Laravel version application using bellow command:

composer create-project laravel/laravel example-app

 

Step 2: Create Migration

We need an extra field like code to verify our sending code. So create it like below:

php artisan make:model UserCode -m

 

And update the newly created migration file like below:

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();
        });
    }
  
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('user_codes');
    }
}

 

And update this model like

app/Models/UserCode.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',
    ];
}

 

And run php artisan migrate to create this table.

 

Step 3: Update User Model

After registration is completed, we need to send a code to verify the user, so we will create this method in the user model to create two-factor authentication in Laravel.

app/Models/User.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;
  
    protected $fillable = [
        'name',
        'email',
        'password'
    ];
  
    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    public function generateCode()
    {
        $code = rand(1000, 9999);
  
        UserCode::updateOrCreate(
            [ 'user_id' => auth()->user()->id ],
            [ 'code' => $code ]
        );
    
        try {
  
            $details = [
                'title' => 'Your two factor authentication code is:',
                'code' => $code
            ];
             
            Mail::to(auth()->user()->email)->send(new SendCodeMail($details));
    
        } catch (Exception $e) {
            info("Error: ". $e->getMessage());
        }
    }
}

 

Step 4: Create Default Auth

If you don't have authentication yet, quickly create this default authentication to check two-factor authentication in Laravel.

composer require laravel/ui
php artisan ui bootstrap --auth

 

Step 5: Create Middleware

In this step, we will create new middleware to check the user has 2fa or not. so let's create new middleware with the following commands and code:

php artisan make:middleware Check2FA

 

And update this middleware like below:

app/Http/Middleware/Check2FA.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 need to register this middleware like:

app/Http/Kernel.php

namespace App\Http;
 
use Illuminate\Foundation\Http\Kernel as HttpKernel;
  
class Kernel extends HttpKernel
{
    ...
    ...
    ...
  
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        ..
        '2fa' => \App\Http\Middleware\Check2FA::class,
    ];
}

 

Step 6: Add Route

We need to create some routes as below:

routes/web.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

First, we need to update the default login controller and need to customize the login method to send a code after login.

app/Http/Controllers/Auth/LoginController.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! Invalid credentials');
    }
}

 

Now update two factor authentication controller like:

app/Http/Controllers/TwoFAController.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.');
    }
}

 

Step 8: Create Mail

Here, we need to create a mail class and need to add configuration to the email file. so let's run the following command and update the file.

php artisan make:mail SendCodeMail

 

And update it like below:

app/Mail/SendCodeMail.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 codecheef.org')
                    ->view('emails.code');
    }
}

 

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

 

And for code, we need to create a blade file like:

resources/views/emails/code.blade.php

 

All are set to go. You can test this two factor authentication system in Laravel using mail by running the following artisan command php artisan serve

 

Read also: Laravel 8 Activate Account after Email Verification Example

 

Hope it can help you.

 

#laravel #laravel-8x #laravel-2fa