Двухфакторная авторизация Google Authenticator


В этой статье речь пойдет о дополнительной защите аккаунта, которую можно добавить при авторизации для дополнительного уровня безопасности. Это, так называемая, двухфакторная аутентификация с использованием приложения Google Authenticator для Android / iPhone.
Под катом я покажу пошаговый процесс внедрения Google API проверки двух факторов на сайте написанного на PHP.


Еще в 2011 году Google запустила двухфакторную аутентификацию G-mail для создания одноразовых токенов входа. Учетные данные пользователей могут быть украдены (например с помощью вирусов или кейлогера, установленного на компьютере жертвы). Если у кого-то вдруг оказался ваш пароль, то 2-х факторная аутентификация может защитить вас от неправомерного использования вашей учетной записи, потому что для входа в вашу учетную запись всегда требуется код безопасности (этот код проверки уникально сгенерирован для вашей учетной записи и обновляется на вашем мобильном телефоне каждые 30-60 секунд) на втором шаге после ввода пароля.

Самый простой способ реализовать двухфакторную аутентификацию Google на вашем веб-сайте PHP – это использовать аутентификатор Google. Аутентификатор Google позволяет осуществлять двухфакторную аутентификацию Google-API для входа в учетную запись Google и для других веб-сайтов. Кроме того, приложение для проверки подлинности Google также доступно в магазинах Android, iPhone и Blackberry, которое основано на следующих двух предлагаемых стандартах:

  • Одноразовый пароль, основанный на времени
  • Одноразовый пароль на основе HMAC
  • Кроме того, в коде PHP будет использоваться пакет Google2FA PHP.

Скачайте исходники

Вы можете скачать все исходники данной статьи по ссылке ниже:
googleauth2.zip [30,15 kB]

Установка Google Authenticator

Загрузите и установите приложение Google Authenticator на мобильное устройство, используйте ссылки ниже под свою систему.


Создание таблицы MySQL

Таблица пользователей содержит все данные о регистрации пользователей, здесь мы будем хранить данные пользователя и уникальный код аутентификации Google.

CREATE TABLE `users` (
`uid` int NOT NULL PRIMARY KEY AUTO_INCREMENT ,
`username` varchar(25) NOT NULL UNIQUE,
`password` varchar(200) NOT NULL ,
`email` varchar(100) NOT NULL,
`name` varchar(100) NOT NULL,
`profile_pic` varchar(200) NOT NULL,
`google_auth_code` varchar(16) NOT NULL /* 16 digit code */
);

У вас должно быть включено расширение PDO для PHP, настройте это в файле конфигурации php.ini

Структура и описание файлов в архиве исходников:

  • googleLib
    • GoogleAuthenticator.php — Библиотека Google Authentication
  • class
    • — userClass.php
  • config.php — Конфиг для подключения к БД
  • index.php — Страница авторизации и регистрации
  • device_confimation.php — Страница подтверждения
  • home.php — Страница приветствия
  • logout.php — Страница выхода
  • session.php — Пользовательская сессия

config.php

Файл конфигурации подключения к базе данных, здесь вы должны изменить имя пользователя, пароль и данные базы данных.

<?php
session_start();
/* DATABASE CONFIGURATION */
define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'username');
define('DB_PASSWORD', 'password');
define('DB_DATABASE', 'databasename');
define("BASE_URL", "http://localhost/PHPLoginHash/"); // Eg. http://yourwebsite.com
    function getDB() 
    {
       $dbhost=DB_SERVER;
       $dbuser=DB_USERNAME;
       $dbpass=DB_PASSWORD;
       $dbname=DB_DATABASE;
       try
       {
            $dbConnection = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass); 
            $dbConnection->exec("set names utf8");
            $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            return $dbConnection;
       }
       catch (PDOException $e)
       {
            echo 'Connection failed: ' . $e->getMessage();
       }
}
?>

userClass.php

Этот класс содержит методы userLogin, userRegistion и userDetails.

<?php
class userClass
{
/* User Login */
  public function userLogin($usernameEmail,$password)
    {
       try
       {
           $db = getDB();
           $hash_password= hash('sha256', $password); //Password encryption 
           $stmt = $db->prepare("SELECT uid FROM users WHERE (username=:usernameEmail or email=:usernameEmail) AND password=:hash_password"); 
           $stmt->bindParam("usernameEmail", $usernameEmail,PDO::PARAM_STR) ;
           $stmt->bindParam("hash_password", $hash_password,PDO::PARAM_STR) ;
           $stmt->execute();
           $count=$stmt->rowCount();
           $data=$stmt->fetch(PDO::FETCH_OBJ);
           $db = null;
           if($count)
           {
               $_SESSION['uid']=$data->uid; // Storing user session value
               $_SESSION['google_auth_code']=$google_auth_code; //Stroing Google authentication code
               return true;
           }
           else
           {
               return false;
           } 
       }
       catch(PDOException $e)
       {
           echo '{"error":{"text":'. $e->getMessage() .'}}';
       }
  }
/* User Registration */
  public function userRegistration($username,$password,$email,$name,$secret)
  {
    try
    {
      $db = getDB();
      $st = $db->prepare("SELECT uid FROM users WHERE username=:username OR email=:email"); 
      $st->bindParam("username", $username,PDO::PARAM_STR);
      $st->bindParam("email", $email,PDO::PARAM_STR);
      $st->execute();
      $count=$st->rowCount();
      if($count<1)
      {
        $stmt = $db->prepare("INSERT INTO users(username,password,email,name,google_auth_code) VALUES (:username,:hash_password,:email,:name,:google_auth_code)");
       $stmt->bindParam("username", $username,PDO::PARAM_STR) ;
       $hash_password= hash('sha256', $password); //Password encryption
       $stmt->bindParam("hash_password", $hash_password,PDO::PARAM_STR) ;
       $stmt->bindParam("email", $email,PDO::PARAM_STR) ;
       $stmt->bindParam("name", $name,PDO::PARAM_STR) ;
       $stmt->bindParam("google_auth_code", $secret,PDO::PARAM_STR) ;
       $stmt->execute();
       $uid=$db->lastInsertId(); // Last inserted row id
       $db = null;
       $_SESSION['uid']=$uid;
       return true;
     }
    else
    {
       $db = null;
       return false;
    }
  } 
  catch(PDOException $e)
  {
    echo '{"error":{"text":'. $e->getMessage() .'}}'; 
  }
}
/* User Details */
  public function userDetails($uid)
  {
    try
    {
      $db = getDB();
      $stmt = $db->prepare("SELECT email,username,name,google_auth_code FROM users WHERE uid=:uid");
      $stmt->bindParam("uid", $uid,PDO::PARAM_INT);
      $stmt->execute();
      $data = $stmt->fetch(PDO::FETCH_OBJ); //User data
      return $data;
    }
    catch(PDOException $e)
    {
      echo '{"error":{"text":'. $e->getMessage() .'}}';
    }
  }
}
?>

index.php

Содержит PHP и HTML-код, принимает от пользователя данные.

<?php
include("config.php");
if(!empty($_SESSION['uid']))
{
  header("Location: device_confirmations.php");
}
include('class/userClass.php');
$userClass = new userClass();
require_once 'googleLib/GoogleAuthenticator.php';
$ga = new GoogleAuthenticator();
$secret = $ga->createSecret(); //This function will create unique 16 digit secret key
$errorMsgReg='';
$errorMsgLogin='';
/* Login Form */
if (!empty($_POST['loginSubmit'])) 
{
  $usernameEmail=$_POST['usernameEmail'];
  $password=$_POST['password'];
  if(strlen(trim($usernameEmail))>1 && strlen(trim($password))>1 )
  {
    $uid=$userClass->userLogin($usernameEmail,$password);
    if($uid)
    {
      $url=BASE_URL.'home.php';
      header("Location: $url"); // Page redirecting to home.php 
    }
    else
    {
      $errorMsgLogin="Please check login details.";
    }
  }
}
/* Signup Form */
if (!empty($_POST['signupSubmit'])) 
{
  $username=$_POST['usernameReg'];
  $email=$_POST['emailReg'];
  $password=$_POST['passwordReg'];
  $name=$_POST['nameReg'];
  /* Regular expression check */
  $username_check = preg_match('~^[A-Za-z0-9_]{3,20}$~i', $username);
  $email_check = preg_match('~^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.([a-zA-Z]{2,4})$~i', $email);
  $password_check = preg_match('~^[A-Za-z0-9!@#$%^&*()_]{6,20}$~i', $password);
  if($username_check && $email_check && $password_check && strlen(trim($name))>0) 
  {
    $uid=$userClass->userRegistration($username,$password,$email,$name);
    if($uid)
    {
      $url=BASE_URL.'home.php';
      header("Location: $url"); // Page redirecting to home.php 
    }
    else
    {
      $errorMsgReg="Username or Email already exists.";
    }
  }
}
?>

Примечание: Для повышения удобства пользователей вы можете написать код проверки вводимых данных на JavaScript.

device_confirmation.php

<?php
include('config.php');
if(empty($_SESSION['uid']))
{
  header("Location: index.php");
}
include('class/userClass.php');
$userClass = new userClass();
$userDetails=$userClass->userDetails($_SESSION['uid']);
$secret=$userDetails->google_auth_code;
$email=$userDetails->email;
require_once 'googleLib/GoogleAuthenticator.php';
$ga = new GoogleAuthenticator();
$qrCodeUrl = $ga->getQRCodeGoogleUrl($email, $secret,'Your Application Name');
?>
// HTML Code:
Enter the verification code generated by Google Authenticator app on your phone.
<div id="img">
<img src='<?php echo $qrCodeUrl; ?>' />
</div>
<form method="post" action="home.php">
 <label>Enter Google Authenticator Code</label>
 <input type="text" name="code" />
 <input type="submit" class="button"/>
</form>

home.php

Страница приветствия пользователя, отображение информации о пользователе на основе значения сеанса пользователя.

<?php
include('config.php');
include('class/userClass.php');
$userClass = new userClass();
$userDetails=$userClass->userDetails($_SESSION['uid']);
if($_POST['code'])
{
  $code=$_POST['code'];
  $secret=$userDetails->google_auth_code;
  require_once 'googleLib/GoogleAuthenticator.php';
  $ga = new GoogleAuthenticator();
  $checkResult = $ga->verifyCode($secret, $code, 2);    // 2 = 2*30sec clock tolerance
  if ($checkResult)
  {
    $_SESSION['googleCode']=$code;
  }
  else
  {
    echo 'FAILED';
  }
}
include('session.php');
$userDetails=$userClass->userDetails($session_uid);
?>
// HTML Code:
<h1>Welcome <?php echo $userDetails->name; ?></h1>
<h2> Email <?php echo $userDetails->email; ?></h2>
<a href="<?php echo BASE_URL; ?>logout.php">Logout</a>

session.php

Проверка и сохранение сессии пользователя.

<?php
if(!empty($_SESSION['uid']) && !empty($_SESSION['googleCode']))
{
  $session_uid=$_SESSION['uid'];
  $session_googleCode=$_SESSION['googleCode'];
}
if(empty($session_uid) && empty($session_googleCode))
{
  $url=BASE_URL.'index.php';
  header("Location: $url");
}
?>

logout.php

Этот код очистит значения сеанса пользователя Google Authenticator.

<?php
include('config.php');
$session_uid='';
$session_googleCode='';
$_SESSION['uid']='';
$_SESSION['googleCode']='';
if(empty($session_uid) && empty($_SESSION['uid']))
{
  $url=BASE_URL.'index.php';
  header("Location: $url");
}
?>

В итоге, должна получится такая форма:

Заключение

Мы рассмотрели пример использования Google Authenticator на PHP.
Преимущество двухфакторной аутентификации через мобильное устройство:

  • Не нужны дополнительные токены, потому что мобильное устройство всегда под рукой.
  • Код подтверждения постоянно меняется, а это безопаснее, чем однофакторный логин-пароль.


Оцените статью или поделитесь ей в соцсетях:

+ 0 | - 0

Рекомендуемые статьи:


Комментарии

  1. Павел 28 декабря 2018

    Смысл от этой двухфакторной авторизации, если Email и Пароль были взломаны? Нужно сделать только при регистрации чтоб 1 раз показывался QRCode и всё.

  2. Старый Айтишник 28 декабря 2018

    Павел, мне кажется Вы не совсем понимаете для чего это нужно. Если злоумышленник узнает пароль, он не войдет в аккаунт, пока не введет код GA.

  3. Павел 29 декабря 2018

    Я не про двухфакторную авторизацию в целом, я про данную реализацию имею ввиду.
    Дело в том, что сама реализация двухфакторной авторизации в данном примере имеет уязвимость.

    Поясняю:
    Скажем я зарегился с помощью скрипта index.php, затем нас редиректит на страницу скрипта device_confimation.php где и показывается QRCode который ты сканируешь с помощью Google Authentication, вводишь с генерируемый код в спец. для этого поле и если всё Ок, то тебя приветствует скрипт home.php

    Всё хорошо!

    Но, при этом я могу выйти и зайти на страницу ещё раз зная логин и пароль и мне снова предлагается страница скрипта device_confimation.php где и показывается QRCode который и ты опять можешь сканировать и войти спокойно.

    Вот от этого вопрос смысл от этой двухфакторной авторизации, если логин и пароль есть у злоумышленника!

    Должно быть как на самом деле:
    При регистрации должен генерироваться и показываться QRCode, а при авторизации нет.

    Те есть нужны две разные страницы регистрации и авторизации, ведь при регистрации генерируется ключ который по факту должен быть уникальным, и нельзя позволять Google Authenticator генерировать один и тот же ключ.

    Не много не то написал, но суть уловили надеюсь.

    «Старый Айтишник мне кажется Вы не совсем понимаете для чего это нужно. Если злоумышленник узнает пароль, он не войдет в аккаунт, пока не введет код GA.»
    Как раз наоборот злоумышленник с генерирует на странице при авторизации device_confimation.php и точно так же у него будет генерируемые ключи в Google Authenticator

  4. Павел 29 декабря 2018

    Те есть нужны две разные страницы регистрации и авторизации, ведь при регистрации генерируется ключ который по факту должен быть уникальным, и нельзя позволять Google Authenticator генерировать один и тот же ключ.

  5. Павел 29 декабря 2018

    Не много не то написал, но суть уловили надеюсь.

  6. Павел 29 декабря 2018

    «Старый Айтишник мне кажется Вы не совсем понимаете для чего это нужно. Если злоумышленник узнает пароль, он не войдет в аккаунт, пока не введет код GA.»

    Как раз наоборот злоумышленник с генерирует на странице при авторизации device_confimation.php и точно так же у него будет генерируемые ключи в Google Authenticator

  7. Старый Айтишник 29 декабря 2018

    Павел, выше это просто пример реализации GA. В продакшен никто не станет тупо копипастить скрипты. Для понимания принципа работы GA, я считаю, достаточно данной реализации.

  8. Павел 29 декабря 2018

    Старый Айтишник, тут я соглашусь с вами, если в качестве примера то да, но зря вы говорите что ни кто не станет копипастить, 50% всех начинающих веб мастеров именно так и делают.

  9. Олег 23 мая 2022

    Этот скрипт хорошо показывает логику аутенфикации, но что делать дальше, чтобы извлечь из него пользу? Сейчас оно просто генерирует код, который считывается Google приложением на любом смартфоне. Зная юзернейм и пароль, злоумышленник на своем смартфоне сканирует код и спокойно входит. Как этого не допустить?

Оставьте комментарий!

Поля обозначенные как * требуются обязательно. Перед постингом всегда делайте просмотр своего комментария.