Статьи

Интеграция двухфакторной аутентификации с CodeIgniter

С недавней чередой громких взломов (хаков) в Sony и других компаниях пришло время взглянуть на безопасность вашего сайта. Двухфакторная аутентификация — это шаг в правильном направлении для защиты вашего сайта от злоумышленников. В этом руководстве мы рассмотрим реализацию этого в нашем приложении CodeIgniter.


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

В последнее время такие компании, как Google и Facebook , внедряют двухфакторную аутентификацию для своих пользователей. Другие службы, такие как MailChimp , используют альтернативные формы двухфакторной аутентификации, чтобы помешать злоумышленникам. Но все же, что конкретно является двухфакторной аутентификацией?

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

Duo поддерживает push-уведомления
Мобильное приложение Duo поддерживает push-уведомления для аутентификации!

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

К счастью для вас, Duo Security предлагает бесплатный двухфакторный сервис, идеальный для тех, кто хочет защитить свой сайт.

Duo не только бесплатен, но и полон функций . Они позволяют вам выполнять аутентификацию различными способами, в том числе:

  • Проверка подлинности телефонного звонка
  • SMS-токены
  • Генератор токенов мобильного приложения
  • Push-аутентификация
  • Аппаратные токены доступны для покупки

Если вы раньше не работали с CodeIgniter, я настоятельно рекомендую вам сначала ознакомиться с серией CodeIgniter From Scratch .

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

Убедитесь, что в вашем config/autoload.php загружены следующие помощники.

1
$autoload[‘helper’] = array(‘url’, ‘form’);

Перейдите в Duo Security и зарегистрируйте учетную запись.

Они предлагают бесплатный план для проектов с открытым исходным кодом и для сайтов с количеством пользователей Duo менее 10 (пользователь Duo — это тот, кто будет использовать двухфакторную аутентификацию для входа в систему).

После регистрации вам необходимо войти в Duo и создать интеграцию. Войдя в систему, нажмите на интеграции на боковой панели, чтобы открыть страницу интеграции. Оттуда, нажмите кнопку «Новая интеграция».

Убедитесь, что созданная вами интеграция является интеграцией Web SDK. Это позволит вам использовать их PHP API с CodeIgniter.

Имя интеграции используется только на сайте Duo. Это просто способ определить вашу интеграцию. Duo имеет руководство по началу работы, которое объясняет, как настроить интеграцию.

В дополнение к настройке интеграции вам необходимо загрузить Web SDK.

Нам понадобятся две части SDK: файл PHP (duo_web.php) и файл JavaScript . Обратите внимание, что JavaScript имеет зависимость от jQuery, а связанный JavaScript поставляется с jQuery.

Мы будем использовать связанный JavaScript, но учтите, что в противном случае jQuery должен быть загружен до JavaScript, предоставляемого Duo. Для получения дополнительной информации о Web SDK и о том, как он работает, см. Документацию по адресу http://www.duosecurity.com/docs/duoweb.


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

В качестве первого шага мы добавим сильную функцию хеширования в базу данных. У Openwall есть хорошая библиотека хэширования PHP, которая реализует bcrypt . Последняя версия phpass 0.3 на момент написания этой статьи.

Загрузите phpass со своего веб-сайта: http://openwall.com/phpass/ . После загрузки и разархивирования папки вам необходимо поместить ее в папку с библиотеками.

Теперь нам нужно создать собственный библиотечный файл в качестве интерфейса для phpass. Создайте новый файл библиотеки с именем password.php. Наша библиотека будет иметь две функции:

  • хэш-функция для перефразирования старых паролей
  • функция check_password для сравнения хэшей с незашифрованными паролями.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
require_once(‘phpass-0.3/PasswordHash.php’);
 
class Password {
 
  var $hasher;
 
  function __construct()
  {
    // 8 is the hash strength.
    // TRUE makes the passwords portable.
    $this->hasher = new PasswordHash(8, TRUE);
  }
   
 function hash($pass)
 {
    return $this->hasher->HashPassword($pass);
 }
  
 function check_password($pass, $hash){
    return $this->hasher->CheckPassword($pass, $hash);
 }
}

Оператор require_once() гарантирует, что мы сможем использовать класс PasswordHash из phpass.

PasswordHash принимает два аргумента в своем конструкторе:

  • число, указывающее на прочность хеша
  • логическое значение относительно того, должны ли пароли быть переносимыми.

В этом случае мы собираемся сделать наши пароли переносимыми.

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public function verify_user($email, $password)
  {
     $q = $this
           ->db
           ->where(’email_address’, $email)
           ->limit(1)
           ->get(‘users’);
     
     if ( $q->num_rows > 0 ) {
        $result = $q->row();
        $this->load->library(‘password’);
         
        //Make sure the hashes match.
        if($this->password->check_password($password, $result->password)){
         return $result;
        }
     }
     return false;
  }

Ранее мы выбирали пользователя по адресу электронной почты и хешированному паролю. Теперь мы извлекаем пользователя из базы данных на основе адреса электронной почты. Это означает, что мы должны подтвердить пароль, прежде чем мы сможем вернуть пользователя.

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

Если два пароля совпадают, мы продолжаем возвращать пользователя, в противном случае мы возвращаем false .

Обязательно используйте библиотеку паролей, чтобы хэшировать новый пароль для себя. Пароли в вашей базе данных теперь будут недействительными!

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

Нам нужно добавить столбец в таблицу пользователей для двухфакторных разрешений. Вы можете сделать это через phpMyAdmin или запустив следующий SQL.

1
ALTER TABLE users ADD two_factor_permission BOOLEAN NOT NULL;

Значение 1 в столбце разрешений заставит пользователя использовать двухфакторную аутентификацию.

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

Если вы сделали это правильно, вы должны увидеть новый столбец в вашей таблице пользователей. Затем вам нужно будет обновить текущую запись или вставить новую запись, которая устанавливает two_factor_permission в true (1).

Если для этого столбца установлено значение false (0), пользователь сможет обойти двухфакторную аутентификацию. Это идеально подходит для пользователей, которым не нужен тот же уровень безопасности, что и для администратора.


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

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

01
02
03
04
05
06
07
08
09
10
11
if ( $res !== FALSE ) {
       $_SESSION[‘username’] = $res->email_address;
       if ( $res->two_factor_permission ) {
         $this->_second_auth($res->email_address);
         return;
       }
       else {
         $_SESSION[‘logged_in’] = TRUE;
         redirect(‘welcome’);
       }
   }

Это проверяет, должен ли пользователь войти в систему с помощью нашей двухфакторной системы.

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

Вместо перенаправления пользователя мы можем вызвать _second_auth() , которая загрузит страницу. Оператор return загружает форму входа.

Мы создали новую переменную сеанса logged_in которую мы будем использовать для проверки того, что пользователь вошел в систему. Это означает, что нам нужно внести некоторые изменения в перенаправления.

Есть два перенаправления, которые необходимо изменить: первое в функции индекса контроллера администратора.

1
2
3
if ( isset($_SESSION[‘logged_in’]) && $_SESSION[‘logged_in’] === TRUE ) {
  redirect(‘welcome’);
}

Другой в welcome контроллере. Мы должны убедиться, что пользователь не вошел в систему перед перенаправлением.

1
2
3
if ( !isset($_SESSION[‘logged_in’]) || $_SESSION[‘logged_in’] !== TRUE ) {
  redirect(‘admin’);
}

Теперь нам нужно обработать вторичную аутентификацию.

В функции admin/index мы вызываем _second_auth() , поэтому давайте напишем базовую функцию.

1
2
3
4
public function _second_auth ($username)
 {
   echo «Welcome $username, you are looking at a secondary authentication page.»;
 }

Традиционные системы аутентификации рассматривают вход в систему как один шаг.

Пользователь вводит имя пользователя и пароль, затем входит в систему

Duo дает нам немного JavaScript и HTML для вставки между двумя шагами. Это означает, что нам нужно создать представление с требуемым кодом.

Пользователь вводит имя пользователя и пароль, затем запрашивается подтверждение телефона, затем входит в систему

Давайте создадим новое представление с именем second_auth.php в папке представлений. Нам нужно будет вставить iframe и JavaScript, предоставляемые Duo, чтобы он работал.

Вы должны создать страницу с базовой структурой HTML. Следующее может быть помещено в тело:

1
2
<iframe id=»duo_iframe» width=»100%» height=»500″ frameborder=»0″></iframe>
       <script type=»text/javascript» src=»/path/to/Duo-Web-v1.js» ></script>

В типичной настройке вы должны хранить весь свой JavaScript в папке ресурсов. Здесь мы поместили папку resources в корень нашего сайта с подпапкой ‘ js ‘, которая содержит файл JavaScript Web SDK.

Наш src будет выглядеть так:

1
src=»<? echo base_url(); ?>resources/js/Duo-Web-v1.js»

Нам также нужно добавить второй бит JavaScript.

Мы сгенерируем эти данные из контроллера в ближайшее время.

Если вы следовали предыдущему руководству , вы должны настроить CodeIgniter для защиты от CSRF .

Поскольку JavaScript будет отправлять данные на наш контроллер, CodeIgniter будет искать токен CSRF. Если у нас нет этого токена, мы получим ошибку.

JavaScript, который мы используем, отправит форму с идентификатором » duo_form «. Все, что нам нужно сделать, это создать его.

1
2
echo form_open(‘admin’, array(‘id’=> «duo_form»));
echo form_close();

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


Вернувшись в контроллер admin , нам нужно сгенерировать некоторые данные в нашей функции _second_auth() .

Хост — это URL-адрес API, который вы указали при регистрации в Duo. Этот URL-адрес должен выглядеть примерно так: api-xxxxxxxx.duosecurity.com (где «xxxxxxxx» — уникальная строка, связанная с вашей учетной записью Duo)

1
$data[‘host’] = «api-xxxxxxxx.duosecurity.com»;

Не забудьте заменить хост своим конкретным URL. Приведенный выше URL не будет работать.

Действие post — это URL-адрес, который будет обрабатывать ответ, как только пользователь попытается пройти аутентификацию в Duo.

Мы создадим еще одну функцию в контроллере администратора для обработки постбэка. Сейчас мы назовем функцию process_second_auth() .

1
$data[‘post_action’] = base_URL() .

Убедитесь, что вы переименовали duo_web.php в duo.php, чтобы избежать ошибок CodeIgniter.

Если вы еще не загрузили последнюю копию duo_web.php , вы можете получить ее на странице GitHub Web SDK Duo.

Поскольку Web SDK поставляется как класс PHP, мы можем переименовать его в « duo.php » и поместить в нашу папку «application / library».

После того, как вы поместили файл в папку libraries , мы можем загрузить его в наш контроллер.

1
2
3
4
5
6
7
8
9
public function _second_auth($username)
 {
   $this->load->library(‘duo’);
    
   $data[‘host’] = «api-xxxxxxxx.duosecurity.com»;
   $data[‘post_action’] = base_URL() .
    
   echo «Welcome $username, you are looking at a secondary authentication page.»;
 }

Чтобы понять, как генерировать sig_request , вы должны понимать, что мы генерируем.

$akey должна иметь длину не менее 40 символов, в противном случае библиотека Duo выдаст ошибку!

Duo Web SDK создает два подписанных токена, один с секретным ключом, который они вам дают, другой с ключом приложения, который вы составляете.

sig_request — это комбинация двух токенов.

Создав свой собственный ключ приложения, вы получите второй уровень безопасности. Злоумышленнику понадобится как секретный ключ от Duo, так и ваш личный ключ приложения, чтобы подделать токен.

Теперь мы сгенерируем ‘sig_request’. Duo предоставит вам ключ интеграции и секретный ключ при создании интеграции.

Обязательно замените приведенный ниже текст на предоставленный вам ключ интеграции и секретный ключ. Вам нужно создать свой собственный секретный ключ. Оно должно быть длиной не менее 40 символов и должно быть как можно более случайным.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public function _second_auth($username)
{
  $this->load->library(‘duo’);
   
  // Duo Integration Key
  $ikey = «REPLACE WITH YOUR DUO INTEGRATION KEY»;
   
  // Duo Secret Key
  $skey = «REPLACE WITH YOU DUO SECRET KEY»;
   
  // Personal Application Key
  $akey = «CREATE AN APPLICATION KEY»;
   
  $data[‘host’] = «api-xxxxxxxx.duosecurity.com»;
  $data[‘post_action’] = base_URL() .
  $data[‘sig_request’] = $this->duo->signRequest($ikey, $skey, $akey, $username);
   
  echo «Welcome $username, you are looking at a secondary authentication page.»;
}

Дуэт signRequest() сгенерирует токены и вернет их в виде строки для передачи в sig_request .

Теперь нам нужно загрузить данные в представление, которое мы создали ранее.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public function _second_auth($username)
{
  $this->load->library(‘duo’);
   
  // Duo Integration Key
  $ikey = «REPLACE WITH YOUR DUO INTEGRATION KEY»;
   
  // Duo Secret Key
  $skey = «REPLACE WITH YOUR DUO SECRET KEY»;
   
  // Personal Application Key
  $akey = «CREATE AN APPLICATION KEY»;
   
  $data[‘host’] = «api-xxxxxxxx.duosecurity.com»;
  $data[‘post_action’] = base_URL() .
  $data[‘sig_request’] = $this->duo->signRequest($ikey, $skey, $akey, $username);
   
  $this->load->view(‘second_auth’, $data);
}

Если вы попытаетесь войти сейчас, вы должны увидеть эту страницу:

Страница регистрации в дуэт

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

Если вы ничего не видите, просмотрите страницу с сообщениями об ошибках. Любые ошибки с данными будут отображаться в <script> .

Если появится сообщение «Доступ запрещен», убедитесь, что вы ввели секретный ключ интеграции с веб-сайта Duo Security.


Мы настроили наше действие post на admin/process_second_auth , поэтому нам нужно создать функцию process_second_auth() .

1
2
3
4
5
6
public function process_second_auth()
{
  if ( isset($_SESSION[‘logged_in’]) && $_SESSION[‘logged_in’] === TRUE ) {
    redirect(‘welcome’);
  }
}

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

Мы должны снова загрузить библиотеку Duo для проверки данных.

1
2
3
4
5
6
$this->load->library(‘duo’);
 
// Same keys used in _second_auth()
$ikey = «REPLACE WITH YOUR DUO INTEGRATION KEY»;
$skey = «REPLACE WITH YOUR DUO SECRET KEY»;
$akey = «REPLACE WITH YOUR APPLICATION KEY»;

Нам $ikey, $skey те же $ikey, $skey и $akey из функции _second_auth() для проверки опубликованных данных.

JavaScript sig_response с серверов Duo.

1
2
$sig_response = $this->input->post(‘sig_response’);
$username = $this->duo->verifyResponse($ikey, $skey, $akey, $sig_response);

Как только мы sig_response из опубликованных данных, мы запустим его с помощью функции verifyResponse() . Это вернет NULL, если токены не совпадают, или имя пользователя, если они действительны.

1
2
3
4
5
6
7
if ( $username ) {
  $_SESSION[‘logged_in’] = TRUE;
  redirect(‘welcome’);
}
else{
  redirect(‘admin’);
}

Наконец, мы проверим, что имя пользователя было возвращено, и завершим его регистрацию, установив значение $_SESSION['logged_in'] в true.

Все вместе функция должна выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public function process_second_auth()
{
  if ( isset($_SESSION[‘logged_in’]) && $_SESSION[‘logged_in’] === TRUE ) {
    redirect(‘welcome’);
  }
   
  $this->load->library(‘duo’);
   
  // Same keys used in _second_auth()
  $ikey = «REPLACE WITH DUO’S INTEGRATION KEY»;
  $skey = «REPLACE WITH DUO’S SECRET KEY»;
  $akey = «REPLACE WITH YOUR APPLICATION KEY»;
   
  $sig_response = $this->input->post(‘sig_response’);
  $username = $this->duo->verifyResponse($ikey, $skey, $akey, $sig_response);
   
  if ( $username ) {
    $_SESSION[‘logged_in’] = TRUE;
    redirect(‘welcome’);
  }
  else{
    redirect(‘admin’);
  }
}

Теперь вы сможете войти в систему с двухфакторной аутентификацией, попробуйте и попробуйте!


Надеюсь, вы установили собственную систему двухфакторной аутентификации для CodeIgniter!

Что еще ты мог сделать? С точки зрения безопасности многое нужно сделать, но самым большим улучшением будет отслеживание действий пользователя.

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

Спасибо за прочтение! Если у вас возникли проблемы, оставьте пост в комментариях.