Home » 2fa Laravel With SMS Tutorial With Example

2fa Laravel With SMS Tutorial With Example

Last updated on May 25, 2022 by

In this article, we will learn 2fa Laravel (Two Factor Authentication) with an SMS tutorial with example. We will use the composer package twilio/sdk to send SMS using the Laravel. Let’s just see how to do it.

Step 1: Install Laravel

If you already have installed Laravel on your local machine then you can skip this step. You can easily install the fresh version of Laravel by running the below command in your terminal. You can give it any name but in this case, we will name it demo-app.

composer create-project --prefer-dist laravel/laravel demo-app

Step 2: Install twilio/sdk Package

Next, we need to install twilio/sdk composer package to use Twilio API. Change your current working directory to demo-app and run the composer command as below:

cd demo-app
composer require twilio/sdk

If you don’t have composer installed on your PC you can do so by following the instructions here.

Step 3: Create Twilio Account

First, we need to create a Twilio account. Then, go to your Twilio dashboard and grab your Account SID and Auth Token.

laravel twillio get credentials

Now go to the Phone Number menu section to get your SMS-enabled phone number.

laravel twillio send sms tutorial get phone number

Step 4: Update .env File

The next step is to update the .env file with your Twilio credentials. So open up .env located at the root of the project directory and add the following values:

TWILIO_SID="INSERT YOUR TWILIO SID HERE"
TWILIO_AUTH_TOKEN="INSERT YOUR TWILIO TOKEN HERE"
TWILIO_NUMBER="+91***************"

Notes: After adding values in the .env file you need to clear the config cache by running the below command:

php artisan config:clear

Step 5: Update User Migration

In this step, we will add otp column in users table.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->text('title')->nullable();
            $table->text('first_name');
            $table->text('middle_name')->nullable();
            $table->text('last_name');
            $table->string('email')->unique();
            $table->string('phone')->nullable();
            $table->string('password')->nullable();
            $table->integer('otp')->nullable();
            $table->rememberToken();
            $table->timestamps();
        });
    }


    public function down()
    {
        Schema::dropIfExists('users');
    }
}

Now you have to run this migration by the following command:

php artisan migrate

Or if you are working in local then you can run the following command but make sure this command will drop all the tables first and recreate it.

php artisan migrate:fresh

Step 6: Update User Model

Now, we need to update the user model and create a new method that will generate the 6-digit OTP code randomly and send it to the user.

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 Twilio\Rest\Client;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    protected $fillable = [
        'title',
        'first_name',
        'middle_name',
        'last_name',
        'email',
        'phone',
        'password',
        'otp',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

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

    public function sendOtp()
    {
        $user = Auth::user();        
        $otp = mt_rand(100000,999999);

        User::updateOrCreate(
            [ 'id' => $user->id ],
            [ 'otp' => $otp ]
        );

        $receiverNumber = $user->phone;

        $message = "Your login code is ". $code;

        try {

            $accountSid = getenv("TWILIO_SID");
            $authToken = getenv("TWILIO_TOKEN");
            $twilioNumber = getenv("TWILIO_FROM");

            $client = new Client($accountSid, $authToken);

            $client->messages->create($receiverNumber, [
                'from' => $twilioNumber, 
                'body' => $message
            ]);

        } catch (Exception $e) {
            info("Error: ". $e->getMessage());
        }
    }
}

Step 7: Create Auth Scaffold

Here, we will use laravel ui package to create auth scaffold with the bootstrap framework which will generate the Login, Register, and other auth-related functionality. Run the below command:

composer require laravel/ui

Now, let’s run the command for creating auth scaffold:

php artisan ui bootstrap --auth

Step 8: Create Middleware

In this step, we will create new middleware to check whether the user has 2fa or not. So let’s create a new middleware with the following command and code:

php artisan make:middleware CheckOtp

app/Http/Middleware/CheckOtp.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;

class CheckOtp
{
    public function handle(Request $request, Closure $next)
    {
        if (!empty(auth()->user()) && !empty(auth()->user()->otp)) {
            return Redirect::route('login.verify');
        }

        return $next($request);
    }
}

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,
        'remember' => \Reinink\RememberQueryStrings::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'check.otp' => \App\Http\Middleware\CheckOtp::class,
    ];
}

Step 9: Create Routes

We need to add routes in routes/web.php file to perform the 2fa Laravel send SMS. The first GET route is used to show the verify OTP form and another route for the POST method to verify the OTP in Laravel.

routes/web.php

<?php

use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\DashboardController;
use Illuminate\Support\Facades\Route;


Route::get('login', [LoginController::class, 'showLoginForm'])
    ->name('login')
    ->middleware('guest');

Route::post('login', [LoginController::class, 'login'])
    ->name('login.attempt')
    ->middleware('guest');

Route::get('verify-login', [LoginController::class, 'showOtpForm'])
    ->name('login.verify');

Route::post('verify-login', [LoginController::class, 'verifyLogin'])
    ->name('verify.attempt');

//Route::get('resend-otp/{isResend}', [LoginController::class, 'sendOtp'])
    //->name('resend.otp')->middleware('throttle:5,1');

Route::group(['middleware' => ['auth', 'check.otp']], function() {
    // Dashboard
    Route::get('/', [DashboardController::class, 'index'])
        ->name('dashboard');
});

Step 10: Create LoginController

Let’s create a LoginController and let’s add two methods, one for the display SMS sending form and the second for the sending messages to the receiver. To create a controller run the below command:

php artisan make:controller LoginController

After running the above command a new file will be created in Controllers the directory, open it, and add the following method.

app/Http/Controllers/LoginController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use App\Http\Requests\Auth\Login;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\RedirectsUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Session;

class LoginController extends Controller
{

    use AuthenticatesUsers, RedirectsUsers, ThrottlesLogins;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;


    public function showLoginForm(Request $request)
    {
        return view('auth.login');
    }

   
    public function showOtpForm()
    {
        return view('auth.verify');
    }

    public function login(Login $request)
    {
        if (method_exists($this, 'hasTooManyLoginAttempts') &&
            $this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

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

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

            $user = Auth::user();

            $this->clearLoginAttempts($request);

            $user->sendOtp();

            return redirect()->route('auth.verify')->withSuccess('Login OTP has been sent to your mobile.');

        } else {
            $this->incrementLoginAttempts($request);

            return Redirect::back()->with('error', 'Invalid credentials.');
        }
    }

    public function verifyLogin(Request $request)
    {   
        $request->validate([
            'otp'=>'required',
        ]);

        $user = User::where('id', auth()->user()->id)
                        ->where('otp', $request->otp)
                        ->first();

        if (!is_null($user)) {
            $request->user()->otp = null;
            $request->user()->save();
            return Redirect::route('dashboard')->with('success', 'Welcome '. $user->first_name);
        }

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


    /**
     * Get the guard to be used during authentication.
     *
     * @return \Illuminate\Contracts\Auth\StatefulGuard
     */
    protected function guard()
    {
        return Auth::guard();
    }
}

Step 11: Create Blade/HTML File

At last, we need to create a verify.blade.php file in the views folder, and in this file, we will add the top verify Form HTML.

resources/views/auth/verify.blade.php

<!DOCTYPE html>
<html>
   <head>
      <title>2FA Laravel With SMS Tutorial Examples - ScratchCode.io</title>
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
   </head>
   <body>
      <div class="container mt-5">
         <div class="panel panel-primary">
            <div class="panel-heading">
               <h2>2FA Laravel With SMS Tutorial Examples - ScratchCode.io</h2>
            </div>
            <div class="panel-body">
               @if ($message = Session::get('success'))
	               <div class="alert alert-success alert-block">
	                  <button type="button" class="close" data-dismiss="alert">×</button>
	                  <strong>{{ $message }}</strong>
	               </div>
               @endif

               @if (count($errors) > 0)
               <div class="alert alert-danger">
                  <strong>Whoops!</strong> There were some problems with your input.
                  <ul>
                     @foreach ($errors->all() as $error)
                     <li>{{ $error }}</li>
                     @endforeach
                  </ul>
               </div>
               @endif

               <form action="{{ route('verify.attempt') }}" method="POST" enctype="multipart/form-data">
                  @csrf
                  <div class="row">

                     <div class="col-md-12">
                        <div class="col-md-6 form-group">
                           <label>Otp:</label>
                           <input type="text" name="otp" class="form-control"/>
                        </div>
                        
                        <div class="col-md-6 form-group">
                           <button type="submit" class="btn btn-success">Verify Otp</button>
                        </div>
                     </div>
                  </div>
               </form>
            </div>
         </div>
      </div>
   </body>
</html>

If you have SSL error then follow the following step:

1. Download the following file – cacert.pem
2. Then download the following file – thawte_Premium_Server_CA.pem
3. Open the second file in a text editor and copy its contents into the first file (cacert.pem at the bottom/end).
Save cacert.pem and add the following lines to your php.ini :

[curl]
curl.cainfo=c:/xampp/php/cacert.pem

Obviously, change the directory to the one where your pem is located. Restart the PHP local server (xampp/wamp). Then it will work flawlessly.

Step 12: Output

Hurray! We have completed all steps for the 2FA Laravel with SMS. Let’s run the below command and see how it’s working.

php artisan serve

After running the above command, open your browser and visit the site below URL:

http://localhost:8000/login

Additionally, read our guide:

  1. Laravel: Blade Switch Case Statement Example
  2. Laravel: Switch Case Statement In Controller Example
  3. Laravel: Change Column Type In Migration
  4. Laravel: Change Column Name In Migration
  5. How To Use Where Date Between In Laravel
  6. How To Add Laravel Next Prev Pagination
  7. Laravel Remove Column From Table In Migration
  8. Laravel: Get Month Name From Date
  9. Laravel: Increase Quantity If Product Already Exists In Cart
  10. How To Update Pivot Table In Laravel
  11. How To Install Vue In Laravel 8 Step By Step
  12. How To Handle Failed Jobs In Laravel
  13. Best Ways To Define Global Variable In Laravel
  14. How To Get Latest Records In Laravel
  15. How To Break Nested Loops In PHP Or Laravel
  16. How To Pass Laravel URL Parameter
  17. Laravel Run Specific Migration
  18. Redirect By JavaScript With Example
  19. How To Schedule Tasks In Laravel With Example
  20. Laravel Collection Push() And Put() With Example

That’s it from our end. We hope this article helped you to learn the 2fa Laravel with SMS tutorial with the example.

Please let us know in the comments if everything worked as expected, your issues, or any questions. If you think this article saved your time & money, please do comment, share, like & subscribe. Thank you for reading this post 🙂 Keep Smiling! Happy Coding!

 
 

Leave a Comment