Join the social network of Tech Nerds, increase skill rank, get work, manage projects...
 
  • How to Use 2fa Google Authentication in CakePHP 3 and Above - 8 Steps Guide

    • 0
    • 0
    • 0
    • 0
    • 1
    • 0
    • 1
    • 0
    • 3.43k
    Comment on it

    About Google Authentication/ 2 step verification

     

    2fa stands for "Two Factor Authentication"  also called Google authentication which provides security to user account. In order to log-in to their account, they need to insert unique code generated by their phone app. This will protect their account to access from others after having login details of user.

     

     

    To enable google authentication or 2 step verification into user accounts the following steps have to be considered:

    • Enable 2fa link
    • Download authi/ google authenticator app
    • User will be able to view page with QR code and key after clicking on enable 2fa link. User will enter key manually into their phone app
    • After getting the code from their phone, insert it on the same page . If this code is right then 2fa google authentication or 2 step verification will be enabled into their account.

     

     

     

    Applying 2fa google authentication in cakephp 3

     

    Step 1: Download Google Authenticator from the following url from github:  https://github.com/PHPGangsta/GoogleAuthenticator

    Now you need to extract this zip and then open folder name pphgangsta and copy this file GoogleAuthenticator.php

     

     

    Step 2: Now create a plugin to enable google authentication in cakephp 3. For this you need to open your project and open plugins folder and inside it create a new folder with name GoogleAuthenticate. Paste this file GoogleAuthenticator.php into this folder.

     


    Step 3: Now go to this file: config/bootstrap.php and paste this code into bootstrap.php

    require_once dirname(dirname(__FILE__)).'/plugins/GoogleAuthenticate/GoogleAuthenticator.php';
    $gauth = new PHPGangsta_GoogleAuthenticator();

    this code will make this class available globally.

     

     

    Step 4: Now create component GoogleAuthenticatorComponent.php in the following directory:

    src/Controller/Component  and Copy and paste the below code in it:

    <?php
    
    namespace App\Controller\Component;
    use Cake\Controller\Component;
    
    class GoogleAuthenticatorComponent extends Component
    {
        protected $_codeLength = 6;
    
        /**
         * Create new secret.
         * 16 characters, randomly chosen from the allowed base32 characters.
         *
         * @param int $secretLength
         *
         * @return string
         */
        public function createSecret($secretLength = 16)
        {
            $validChars = $this->_getBase32LookupTable();
    
            // Valid secret lengths are 80 to 640 bits
            if ($secretLength < 16 || $secretLength > 128) {
                throw new Exception('Bad secret length');
            }
            $secret = '';
            $rnd = false;
            if (function_exists('random_bytes')) {
                $rnd = random_bytes($secretLength);
            } elseif (function_exists('mcrypt_create_iv')) {
                $rnd = mcrypt_create_iv($secretLength, MCRYPT_DEV_URANDOM);
            } elseif (function_exists('openssl_random_pseudo_bytes')) {
                $rnd = openssl_random_pseudo_bytes($secretLength, $cryptoStrong);
                if (!$cryptoStrong) {
                    $rnd = false;
                }
            }
            if ($rnd !== false) {
                for ($i = 0; $i < $secretLength; ++$i) {
                    $secret .= $validChars[ord($rnd[$i]) & 31];
                }
            } else {
                throw new Exception('No source of secure random');
            }
    
            return $secret;
        }
    
        /**
         * Calculate the code, with given secret and point in time.
         *
         * @param string   $secret
         * @param int|null $timeSlice
         *
         * @return string
         */
        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);
        }
    
        /**
         * Get QR-Code URL for image, from google charts.
         *
         * @param string $name
         * @param string $secret
         * @param string $title
         * @param array  $params
         *
         * @return string
         */
        public function getQRCodeGoogleUrl($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.'';
        }
    
        /**
         * Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now.
         *
         * @param string   $secret
         * @param string   $code
         * @param int      $discrepancy      This is the allowed time drift in 30 second units (8 means 4 minutes before or after)
         * @param int|null $currentTimeSlice time slice if we want use other that time()
         *
         * @return bool
         */
        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)) {
                    //die('hello');
                    return true;
                }
            }
    
            return false;
        }
    
        /**
         * Set the code length, should be >=6.
         *
         * @param int $length
         *
         * @return PHPGangsta_GoogleAuthenticator
         */
        public function setCodeLength($length)
        {
            $this->_codeLength = $length;
    
            return $this;
        }
    
        /**
         * Helper class to decode base32.
         *
         * @param $secret
         *
         * @return bool|string
         */
        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;
        }
    
        /**
         * Get array with all 32 characters for decoding from/encoding to base32.
         *
         * @return array
         */
        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
            );
        }
    
        /**
         * A timing safe equals comparison
         * more info here: http://blog.ircmaxell.com/2014/11/its-all-about-time.html.
         *
         * @param string $safeString The internal (safe) value to be checked
         * @param string $userString The user submitted (unsafe) value
         *
         * @return bool True if the two strings are identical
         */
        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]));
            }
    
            // They are only identical strings if $result is exactly 0...
            return $result === 0;
        }
    }
    

     


     

    Step 5: Create usersController if not created, and paste this after namespace

    use GoogleAuthenticator\GoogleAuthenticator;


     

    Step 6: Also you need to add 2 fields in your table: 2fa_key and 2fa_status. Now create a new function "authi" into Users Controller. You need to load the component by writing the code:

     $this->loadComponent('GoogleAuthenticator');

     

    Write the following code:

    public function authi(){
    
        $this->loadComponent('GoogleAuthenticator');
        $ga = $this->GoogleAuthenticator;
        $users = TableRegistry::get('Users');
        $user_cur_id =  '2'; // Put your user id here
        $getcomp_user = $users->get($user_cur_id);
        $userstatus =  $getcomp_user['2fa_status'];
        $userkey =  $getcomp_user['2fa_key'];
        if($userstatus=="active" && $userkey!="") {
            $this->redirect(array("controller" => "Users","action" => "index"));
        }
        if($this->request->is('post'))
        {
            $checkconfirm = $this->request->data['checkconfirm'];
            if($checkconfirm ==0)
            {
                $this->Flash->error(__('Please back up your 16-digit key before proceeding.'),array('class' => 'alert alert-danger'));
                $this->redirect(array("controller" => "Users","action" => "authi"));
            }
            $secret = $this->request->data['secretcode'];
            $oneCode = $this->request->data['code'];
            $checkResult = $ga->verifyCode($secret, $oneCode, 2);    // 2 = 2*30sec clock tolerance
    
    if ($checkResult) {
            $savedata['2fa_key'] =  $this->request->data['secretcode'];
            $savedata['2fa_status'] = "active";
            $curuser = $this->Users->get($user_cur_id);       
            $userupdate = $this->Users->patchEntity($curuser,$savedata);
            if ($this->Users->save($userupdate)) {
                $this->Flash->success(__('Two-Factor Authentication (2FA) Is Enabled.'),array('class' => 'alert alert-danger'));
                $this->redirect(array("controller" => "Users","action" => "index"));
                }else {
                    $this->Flash->error(__('Please try again.'),array('class' => 'alert alert-danger'));
                }
            }else {
                $this->Flash->error(__('Wrong code entered.Please try again.'),array('class' => 'alert alert-danger'));
    
            }
        }
      }

     

     

     

    Step 7: Now create authi.ctp inside template/users directory and paste this code:

     <?php $ga = new PHPGangsta_GoogleAuthenticator();
    
    $secret = $ga->createSecret();
    
    $oneCode = $ga->getCode($secret);
    echo "Checking Code '$oneCode' and Secret '$secret':\n";
    
    $qrCodeUrl = $ga->getQRCodeGoogleUrl('dc-ex.com', $secret);
    ?>
    <div class="col-sm-12 col-md-12 nopadding">
            <div class="col-sm-2 col-md-2 paddingone"></div>
        <div class="col-sm-8 col-md-8 paddingone">
            <div class="users form">
    
    
            <?= $this->Form->create() ?>
               
                 <div class="col-md-12 minheight nopadding">
           
                <div class="content-wrap ">
                <div class="content-box-large">
                <div class="col-md-12">
                    <?= $this->Flash->render('auth') ?>
                    <?= $this->Flash->render() ?>
                </div>              
                               
                <div class="col-md-12">
                    <div class="col-sm-6">
                         <legend>Two Factor Authentication</legend>
    
                         <div class="form-group">
                                <?= $this->Form->input('code', array('type' => 'text','class' => 'form-control','label' => 'Code','required'=>true)); ?>
                          
                             <?= $this->Form->input('secretcode', array('type' => 'hidden','class' => 'form-control','value' => $secret)); ?>
    
    
                            </div>
                               
    
                         <div class="col-sm-12 paddingleftnone">
                                <div class="col-sm-1 paddingleftnone paddingrightnone">
                                    <?= $this->Form->input('checkconfirm', ['type' => 'checkbox','class' => 'form-control','value'=>'1','required'=>true,'label'=>'']); ?>
                                </div>
                                <div class="col-sm-11 aligncenter">I have backed up my 16-digit key.</a>  </div>   
                         </div> 
                   
                      <?= $this->Form->button('Enable 2FA', array('div' => false,'class' => 'btn btn-primary signup', 'title' => 'Enable 2FA')); ?>
    
    
    
                    </div>
                    <div class="col-sm-1"></div>
                    <div class="col-sm-5">
                        <div class="col-sm-12">
                                <label><?php echo "Secret Key is: ".$secret."\n\n"; ?></label>
                            </div>
                            <div class="col-sm-12">
                                <img src="<?php  echo $qrCodeUrl; ?>" name="qr" />
                            </div>
    
                    </div> 
                </div>
                     <div class="clearboth"></div>            
     
                                   
                </div>
                      </div>
                       
                        </div>
      <?= $this->Form->end() ?>
                     </div>
         </div>
    </div>

     

     

    Step 8: You can create your menu and link authi controller with anchor like this:

    <?php echo $this->Html->link('Active 2FA', ['controller'=>'Users', 'action'=>'authi','_full'=>true],['escape' => false]); ?>

     

    After clicking on this link you will be redirected to the page as shown below:

     

     

    Now user will be able to google authenticator into their account, and you can check it on login that user has enabled 2fa security or not.

     

    How you liked the info on Google Authentication. Feel free to share feedbacks and queries in the comment section below.

    How to Use 2fa Google Authentication in CakePHP 3 and Above - 8 Steps Guide

 1 Comment(s)

  • Hi, Can you tell me one thing? I will use it for on my existing project on login parts. So for users first time this will be null in 2fa_key value on db. That time user want to save first time secret key on db, correct? when 2nd time comes what need to check? This stored key value expired when?
Sign In
                           OR                           
                           OR                           
Register

Sign up using

                           OR                           
Forgot Password
Fill out the form below and instructions to reset your password will be emailed to you:
Reset Password
Fill out the form below and reset your password: