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.
1 Comment(s)