Sunday 26 January 2020

Integrating Google Authenticator (2 Factor Authentication) into your PHP Website


What is Two Factor Authentication?

Two Factor Authentication, also known as 2FA, two step verification or TFA (as an acronym), is an extra layer of security that is known as “multi factor authentication” that requires not only a password and username but also something that only, and only, that user has on them, i.e. a piece of information only they should know or have immediately to hand — such as a physical token.

How Google Authenticator works?

Google Authenticator is a free app for your smart phone that generates a new code every 30 seconds. It works like this:
When enabling 2FA, the application you’re securing generates a QR code that user’s scan with their phone camera to add the profile to their Google Authenticator app.
Your user’s smart phone then generates a new code every 30 seconds to use for the second part of authentication to the application.

Implementing Google Authenticator on your website using PHP

For implementing we have to do two steps.


1) User have to scan a barcode and authenticate the two factor authentication. So the secret we will save in a field of our database table. Practically in our live project. suppose you have a table called users in your database. You may be saving all user details like name,email,phone,password all in that table. You have to create 2 fields in that. one field is to check whether user want 2FA or not. so You can set a variable called "2fa_enable" and set as INT or Boolean. so values will be 1 for enabling, 0 for disabled. The second field will be "auth_secret". This field you can create as VARCHAR. It will save all aphanumerals, which will be used to save the secret generated during the authentication.



2)Next step is veryfing the code after the login process. In this step we will check the string enetered using the google authenticator app to the string created using the secret saved in our database. If both matches we will redirect to accessing pages otherwise login will be failed.


Implementing in the Project

1) We are using Authenticator Library for this.  Make a php file named Authenticator.php ad copy paste the below code.

<?php


class Authenticator
{
    protected $length = 6;
    public function generateRandomSecret($secretLength = 16)
    {
        $secret = '';
        $validChars = array(
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 
            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 
            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 
            'Y', 'Z', '2', '3', '4', '5', '6', '7', 
            '=',
        );

        // Valid secret lengths are 80 to 640 bits
        if ($secretLength < 16 || $secretLength > 128) {
            throw new Exception('Bad secret length');
        }
        $random = false;
        if (function_exists('random_bytes')) {
            $random = random_bytes($secretLength);
        } elseif (function_exists('mcrypt_create_iv')) {
            $random = mcrypt_create_iv($secretLength, MCRYPT_DEV_URANDOM);
        } elseif (function_exists('openssl_random_pseudo_bytes')) {
            $random = openssl_random_pseudo_bytes($secretLength, $cryptoStrong);
            if (!$cryptoStrong) {
                $random = false;
            }
        }
        if ($random !== false) {
            for ($i = 0; $i < $secretLength; ++$i) {
                $secret .= $validChars[ord($random[$i]) & 31];
            }
        } else {
            throw new Exception('Cannot create secure random secret due to source unavailbility');
        }

        return $secret;
    }


    public function getCode($secret, $timeSlice = null)
    {
        if ($timeSlice === null) {
            $timeSlice = floor(time() / 30);
        }

        $secretkey = $this->debase32($secret);

        $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice);
        $hm = hash_hmac('SHA1', $time, $secretkey, true);
        $offset = ord(substr($hm, -1)) & 0x0F;
        $hashpart = substr($hm, $offset, 4);

        $value = unpack('N', $hashpart);
        $value = $value[1];
        $value = $value & 0x7FFFFFFF;

        $modulo = pow(10, $this->length);

        return str_pad($value % $modulo, $this->length, '0', STR_PAD_LEFT);
    }


    public function getQR($name, $secret, $title = null, $params = array())
    {
        $width = !empty($params['width']) && (int) $params['width'] > 0 ? (int) $params['width'] : 200;
        $height = !empty($params['height']) && (int) $params['height'] > 0 ? (int) $params['height'] : 200;
        $level = !empty($params['level']) && array_search($params['level'], array('L', 'M', 'Q', 'H')) !== false ? $params['level'] : 'M';

        $urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.'');
        if (isset($title)) {
            $urlencoded .= urlencode('&issuer='.urlencode($title));
        }

        return 'https://chart.googleapis.com/chart?chs='.$width.'x'.$height.'&chld='.$level.'|0&cht=qr&chl='.$urlencoded.'';
    }

    public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null)
    {

   

        if ($currentTimeSlice === null) {
            $currentTimeSlice = floor(time() / 30);
        }

        if (strlen($code) != 6) {
            return false;
        }

    

        for ($i = -$discrepancy; $i <= $discrepancy; ++$i) {
           $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i);
       
            if ($this->timingSafeEquals($calculatedCode, $code)) {
                return true;
            }
        }

        return false;
    }


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

        return $this;
    }


    protected function debase32($secret)
    {
        if (empty($secret)) {
            return '';
        }

        $base32chars =  array(
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 
            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 
            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 
            'Y', 'Z', '2', '3', '4', '5', '6', '7', 
            '=',
        );
        $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;
    }


    private function timingSafeEquals($safeString, $userString)
    {
        if (function_exists('hash_equals')) {
            return hash_equals($safeString, $userString);
        }
        $safeLen = strlen($safeString);
        $userLen = strlen($userString);

        if ($userLen != $safeLen) {
            return false;
        }

        $result = 0;

        for ($i = 0; $i < $userLen; ++$i) {
            $result |= (ord($safeString[$i]) ^ ord($userString[$i]));
        }
        return $result === 0;
    }

}
?>

2) Next step is making the 2factor authentication scanning barcode section. Just refer above points explained whywe are doing it. Here Iam going to explain how to generate the particular QR code for scanning and saving it in database.

Make a File called authentication.php. you can use any name as your preference. You can also use your own styles.

<?php
require "Authenticator.php";

$Authenticator = new Authenticator();

if (!isset($_SESSION['auth_secret'])) {
    $secret = $Authenticator->generateRandomSecret();
    $_SESSION['auth_secret'] = $secret;
}

$siteusernamestr= "Your Sites Unique String";

$qrCodeUrl = $Authenticator->getQR($siteusernamestr, $_SESSION['auth_secret']);


if (!isset($_SESSION['failed'])) {
    $_SESSION['failed'] = false;
}


?>

Here we are calling the Authenticator class. Then script is generating a random secret code if its not generated and saved in session variable. Then  you have to pass a site unique string. which will be unique for the site and the user. You can use a string concatenate with username or users email for this. Then it is passing to a function to generate QR Code.


3. Include this form down the page. Here its displaying the qrcode to scan and the checkbox to enable authentication. put action page as authenticationsubmit.php

<form class="form-horizontal" role="form" method="post" enctype="multipart/form-data" action="authenticationsubmit.php">

<img style="text-align: center;;" class="img-fluid" src="<?php   echo $qrCodeUrl ?>" alt="Verify this Google Authenticator">

<input  class="ace ace-switch ace-switch-5"  type="checkbox" name="twofa_enable" <?php if($res[0]['twofa_enable']==1){ ?>   checked="true" <?php } ?>>
<span class="lbl">Enable Two factor Authentication</span>

<button class="btn btn-info" type="submit" name="submit">
<i class="ace-icon fa fa-check bigger-110"></i>
Submit

</button>
       </form>


4. In authenticationsubmit.php page, just we have to save the variable values into the database.

<?php

if(isset($_POST["submit"])){
   
$twofaenable=$_POST['twofa_enable'];

if($twofaenable=="on"){
$sfen=1;
}else{
$sfen=0;
}
$id=$_SESSION['loggeduser'];

$auth_secret= $_SESSION['auth_secret'];

$options=array('twofa_enable'=>$sfen,'auth_secret'=>$auth_secret);

$pf = user::byId($id);
 $r=$pf->save($options);

$message    =   new Message('2FA Enabled Successfully','message');
$message->setMessage();         
header('Location:authentication.php?msg=success&id='.$id);


   }
?>

Here You can see the particular script is reading values twofa_enable and from session fetch both users id and auth_secret and will update into database.

Now the enabling part is over..

5) Now next step is verifying 2FA code on Login. For this first step in after login details got submitted. From the script you have to check, whether user has set twofa_enable as 1. If it is 1 , we have to redirect users to verifyauthentication page. If the twofa_enable  is 0, we can direct them to homepage without any verification, since the 2fa is not enabled.




First step is including a form from where to collect the verification code entered by the user.


<form  method="post" enctype="multipart/form-data" action="verifyauthentication.php">
<input class="form-control" type="text" name="code"  placeholder="Verify Code">

<button type="submit" class="btn btn-primary" name="submit">
Submit

</button>
</form>

6) Now user will open their google authenticator app and will put the code in the particular code input text area.
7) Now in our submission script we have to read the code entered and along with the users auth_secret value , using a function it will match the code to the valid code. If its is matching, the script will allow login , otherwise it will redirect to login page again. 

<?php
if(isset($_POST['submit'])){
     session_start();
 require "Authenticator.php";

if ($_SERVER['REQUEST_METHOD'] != "POST") {
    header("location: index.php?error=4");
    die();
}

$id=$_SESSION['loggeduser'];
$obj    =   new User();
$res   =   $obj->getLoggedInfo($id); 
$auth_secret =$res[0]['auth_secret'];

$Authenticator = new Authenticator();
$checkResult = $Authenticator->verifyCode($auth_secret,$_POST['code'], 2);    // 2 = 2*30sec clock tolerance

if (!$checkResult) {
    $_SESSION['failed'] = true;
    header("location: login.php?error=4");
    exit;
} else{

header("Location:home.php");
exit;

}

}


  ?>

Here you can see what the script is doing. Its taking the user id from session and readuing the auth_secret value from the database. Then it is calling verifyCode() method from the Authenticator class passing the code submitted by user and the secret we already saved in db. In script , it will match the code with the calculated code. If both are same, the script will redirect you to inner page otherwise will redirect back to login page.



4 comments:

  1. Thank you for this tutorial. In step #4 it appears to save to a database:

    $pf = user::byId($id);
    $r=$pf->save($options);

    This is not standard MySQL... is it a special framework?

    ReplyDelete
    Replies
    1. Its just a saving function... You can use your own function for that.. you can refer the library i used in this post:- https://phpdudes.blogspot.com/2016/06/php-mysqli-framework-for-php.html

      Delete
  2. The saving to the database isn't totally clear...

    ReplyDelete
    Replies
    1. Refer the library function link:- https://phpdudes.blogspot.com/2016/06/php-mysqli-framework-for-php.html
      Hope you will get it..

      Delete