Securing Your Laravel App: Implementing Google Two-Factor Authentication with Laravel

 

In This Tutorial ,I will show you how to use google two factor authentication using laravel with out any packages .we dont need any package it simple easy to use. first of all we need a app from playstore. and below i will show complete code for google two factor. basically this app use for barcode scan then you get a code for authentication. below I have shared the link of the app

By integrating Google Authenticator into your Laravel application, users are required to provide a secondary verification code along with their password, thereby fortifying the login process.


For the Authenticator app, users can download the Google Authenticator app from the Play Store Link : https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2


Route :

Route::get('/twofactor', [TwofactorController::class, 'index'])->name('twofactor.index');
Route::post('/save-twofactor', [TwofactorController::class, 'saveData'])->name('twofactor.save');

First i will show how to enabled and disabled two factor then i show you login process.route create done second is controller below i will show you controller in this code not google related no package installed please follow the code carefully.

Controller : TwofactorController.php

  <?php

    namespace App\Http\Controllers;

    use App\Exports\BladeExport;
    use App\Http\Controllers\Controller;
    use App\Models\User;
    use Auth;
    use DB;
    use Excel;
    use Hash;
    use Illuminate\Http\Request;
    use Illuminate\Support\Arr;
    use Response;
    use Session;
    use Spatie\Permission\Models\Role;

    class TwofactorController extends Controller
    {
        /**
         * Display a listing of the resource.
         *
         * @return \Illuminate\Http\Response
         */
        public function __construct()
        {
            $this->middleware('auth');
        }

   
        public function index(Request $request){
        // echo 'hi';die;
                    $user = Auth::user();
                    $data = array();
                    $secret = $this->createSecret();
                    Session::put('tfa_uid', $user->id);
                    Session::put('two_factor', $user->enable_tfa);
                    Session::put('tfa_enabled_secret', $secret);
                    Session::put('tfa_enabled_pass',$user->password);
                    $url= $this->getQRCodeGoogleUrl($user->email, $secret,$user->account_name);
                    Session::put('tfa_enabled_secret_url', $url);
                    if($user->enable_tfa != 1){                    
                        $auth_code = array('google_auth_code'=>$secret,'google_authentication_url'=>$url);
                        DB::table('users')->where('id',$user->id)->update($auth_code);
                    }                        
                    $data['enable_tfa'] = $user->enable_tfa;
                    $data['qrCodeUrl'] = $url;
                    $data['tfa_enabled_secret']=$secret;
                // echo '<pre>';print_r($user);die;
                    return view('two_factor.index', compact('data'));
        }


        public function saveData(Request $request){
        // echo '<pre>';print_r($request->all());die;
            $this->validate($request, [
                'code' => 'required'
            ]);
            $google_auth_code = Auth::user()->google_auth_code;
            $enable_tfa = Auth::user()->enable_tfa;
            $user_id = Auth::user()->id;
            $checkResult = $this->verifyCode($google_auth_code, $request->code, 2);
            if(!empty($checkResult)){
                if($enable_tfa == 1){
                    $upd = array('enable_tfa' => 2);
                    DB::table('users')->where('id',$user_id)->update($upd);
                return redirect()->back()->with('success', 'Two factor authentication is disabled');
                }else{
                    $upd = array('enable_tfa' =>1);
                    DB::table('users')->where('id',$user_id)->update($upd);
                    return redirect()->back()->with('success', 'Two factor authentication is enabled');
                }
            }else{
                return redirect()->back()->with('error', 'Two factor code is not vaild');
            }
            // print_r($checkResult);
        }


        protected $_codeLength = 6;
        public function createSecret($secretLength = 16){
            $validChars = $this->_getBase32LookupTable();
            unset($validChars[32]);
            $secret = '';
            for ($i = 0; $i < $secretLength; $i++) {
                $secret .= $validChars[array_rand($validChars)];
            }
            return $secret;
        }


        public function getCode($secret, $timeSlice = null){
            if ($timeSlice === null) {
                $timeSlice = floor(time() / 30);
            }
            $secretkey = $this->_base32Decode($secret);
            // Pack time into binary string
            $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice);
            // Hash it with users secret key
            $hm = hash_hmac('SHA1', $time, $secretkey, true);
            // Use last nipple of result as index/offset
            $offset = ord(substr($hm, -1)) & 0x0F;
            // grab 4 bytes of the result
            $hashpart = substr($hm, $offset, 4);
            // Unpak binary value
            $value = unpack('N', $hashpart);
            $value = $value[1];
            // Only 32 bits
            $value = $value & 0x7FFFFFFF;
            $modulo = pow(10, $this->_codeLength);
            return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT);
        }

        public function getQRCodeGoogleUrl($name, $secret, $title = null) {
            $urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.'');
            if(isset($title)) {
                $urlencoded .= urlencode('&issuer='.urlencode($title));
            }
            return 'https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl='.$urlencoded.'';
        }

        public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null){
            if ($currentTimeSlice === null) {
                $currentTimeSlice = floor(time() / 30);
            }
            for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
                $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i);
                if ($calculatedCode == $code ) {
                    return true;
                }
            }
            return false;
        }

        public function setCodeLength($length){
            $this->_codeLength = $length;
            return $this;
        }

        protected function _base32Decode($secret){
            if (empty($secret)) return '';
            $base32chars = $this->_getBase32LookupTable();
            $base32charsFlipped = array_flip($base32chars);
            $paddingCharCount = substr_count($secret, $base32chars[32]);
            $allowedValues = array(6, 4, 3, 1, 0);
            if (!in_array($paddingCharCount, $allowedValues)) return false;
            for ($i = 0; $i < 4; $i++){
                if ($paddingCharCount == $allowedValues[$i] &&
                    substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) return false;
            }
            $secret = str_replace('=','', $secret);
            $secret = str_split($secret);
            $binaryString = "";
            for ($i = 0; $i < count($secret); $i = $i+8) {
                $x = "";
                if (!in_array($secret[$i], $base32chars)) return false;
                for ($j = 0; $j < 8; $j++) {
                    $x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
                }
                $eightBits = str_split($x, 8);
                for ($z = 0; $z < count($eightBits); $z++) {
                    $binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:"";
                }
            }
            return $binaryString;
        }

        protected function _base32Encode($secret, $padding = true){
            if (empty($secret)) return '';
            $base32chars = $this->_getBase32LookupTable();
            $secret = str_split($secret);
            $binaryString = "";
            for ($i = 0; $i < count($secret); $i++) {
                $binaryString .= str_pad(base_convert(ord($secret[$i]), 10, 2), 8, '0', STR_PAD_LEFT);
            }
            $fiveBitBinaryArray = str_split($binaryString, 5);
            $base32 = "";
            $i = 0;
            while ($i < count($fiveBitBinaryArray)) {
                $base32 .= $base32chars[base_convert(str_pad($fiveBitBinaryArray[$i], 5, '0'), 2, 10)];
                $i++;
            }
            if ($padding && ($x = strlen($binaryString) % 40) != 0) {
                if ($x == 8) $base32 .= str_repeat($base32chars[32], 6);
                elseif ($x == 16) $base32 .= str_repeat($base32chars[32], 4);
                elseif ($x == 24) $base32 .= str_repeat($base32chars[32], 3);
                elseif ($x == 32) $base32 .= $base32chars[32];
            }
            return $base32;
        }

        protected function _getBase32LookupTable(){
            return array(
                'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', //  7
                'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
                'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
                'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
                '='  // padding char
            );
        }

   
    }


View : Views/two_factor/index.blade.php


@extends('layouts.app')
@section('title','TWO FACTOR AUTH')
@section('content')
<div class="card card__theme shadow-sm rounded-lg">
<h5 class="card-header">
   <span><i class="bi bi-grid-1x2-fill mr-2"></i>Google two Factor Authentication</span>
   <nav class="dbbreadcrumb" aria-label="breadcrumb">
      <ol class="breadcrumb py-1">
         <li class="breadcrumb-item" aria-current="page"><i class="bi bi-house-door-fill"></i>Google two Factor Authentication</li>
      </ol>
   </nav>
</h5>
<div class="py-3 V-Dashboard-bg">
   <div class="section__container ">
      @include('partials.message')
      @if ($message = Session::get('error'))
      <div class="alert alert-danger container-fluid px-3 pt-3 pb-0 m-0 border-0 justify-content-between text-center" id="msg_success">
         <p>{{ $message }}</p>
      </div>
      <script>
         setInterval(function(){ $('#msg_success').remove(); }, 3000);
       </script>
      @endif
      <form action="{{route('twofactor.save')}}" method="post">
         @csrf
         <div class="row ">
            <div class="col-md-6">
               <div class="row">
                  <div class="col-md-12">
                     <?php if($data['enable_tfa'] != 1){ ?>
                     <div class="form-group">
                        <img src="<?php echo $data['qrCodeUrl']; ?>">
                     </div>
                     <div class="form-group">
                        <p>Provided Key : <?php echo $data['tfa_enabled_secret']; ?></p>
                     </div>
                     <?php } ?>        
                  </div>
                  <div class="col-md-12">
                     <div class="form-group">
                        <!--   <label class="control-label">Two Factor Code</label> -->
                        <input type="text" class="form-control validate" autocomplete="off" id="code" name="code" data-validate-msg=""  placeholder="Enter Two Factor Code" value=""><br>
                        <?php if($data['enable_tfa'] == 1){ ?>        
                        <button class="btn btn-primary saveButton" type="submit">Disable</button>
                        <?php }else{ ?>
                        <button class="btn btn-primary saveButton" type="submit">Enable</button>
                        <?php } ?>
                     </div>
                  </div>
               </div>
            </div>
         </div>
      </form>
   </div>
</div>
@endsection

Output :






After Googla TFA enabled and disabled i will show you login process.please follow the code below


    public function login(SmppServiceInterface $smpp,Request $request){
           
$this->validate($request, [
                'account_name' => 'required',
            // 'email' => 'required|email|unique:users,email,'.$id,
                'password' => 'required'
            //  'roles' => 'required'
            ],[
                'account_name.required' => 'Username field is required',
            // 'email' => 'required|email|unique:users,email,'.$id,
                'password.required' => 'Password field is required'
            //  'roles' => 'required'
            ]);
            $password = $request->password;
            //echo $password;die;
           
       
            $credentials = $request->only('account_name', 'password');
            //print_r($credentials);die;
            if(filter_var($request->account_name, FILTER_VALIDATE_EMAIL)){
            $login = ['email'=>$request->account_name,'password'=>$request->password];
            }else{
            $login = ['account_name'=>$request->account_name,'password'=>$request->password,'status'=>1];
            }
            // echo $request->account_name;
            $getUser = DB::table('users')->where('account_name',$request->account_name)->first();
            // print_r($getUser);die;
            if(isset($getUser) && Hash::check($password,$getUser->password)){
                if($getUser->enable_tfa ==1){
                   
                    return view('auth.twofactor', compact('getUser'));
                }
            }

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

            // echo '<pre>';print_r(Auth::user());die;
                // if success login
            return redirect('dashboard')->with('success','Successfully Login!');

                //return redirect()->intended('/details');
            }
            // if failed login
            return redirect('login')->withErrors(['account_name'=>'Invalid user id or password']);
      }


Users table must be enable_tfa,google_auth_code,google_authentication_url
if any user enabled google two factor then redirect another in login function.new page because the code put here and below i will show how to verify google code.

   
    public function twoLogin(Request $request){
        // echo '<pre>';print_r($request->all());die;
          $this->validate($request, [
             'code' => 'required'
         ]);
           $google_auth_code = Auth::user()->google_auth_code;
           $enable_tfa = Auth::user()->enable_tfa;
            $user_id = Auth::user()->id;
          $checkResult = $this->verifyCode($google_auth_code, $request->code, 2);
          if(!empty($checkResult)){
             return redirect()->route('dashboard')->with('success', 'Two factor       authentication is disabled');
          }else{
              return redirect()->back()->with('error', 'Two factor code is not     vaild');
          }
         // print_r($checkResult);
     }



By following these steps, you can successfully integrate Google Two-Factor Authentication into your Laravel application, bolstering its security measures and safeguarding user accounts against unauthorized access.
Previous Post Next Post

Contact Form