Статьи

Аутентифицировать пользователей с помощью Mozilla Persona

В этой статье мы увидим, как использовать новую, готовую к будущему систему распределенной аутентификации Mozilla Persona . Как следует из названия, Persona создана и спонсируется Mozilla Foundation и представляет собой более простую и безопасную альтернативу OpenID и OAuth.

Теория позади Мозиллы Персона

Mozilla Persona основана на технологии BrowserID, также разработанной Mozilla. Вы можете найти более подробное объяснение понятий в статье « Как работает BrowserID » Ллойда Хайлаэля, но я кратко остановлюсь здесь на ключевых моментах.

  • Адрес электронной почты является идентификатором, и его проверяет поставщик электронной почты (например, Gmail, Yahoo и т. Д.), Который выполняет роль основного органа по идентификации (или просто основного ).
  • Аутентификация происходит в браузере. Нет транзакций со сторонними сайтами, поэтому она эффективна и защищает конфиденциальность. Браузер выполняет роль поставщика реализации ( IP ).

В идеальном (будущем) рабочем процессе браузер сам реализует API navigator.id и поставщики электронной почты могут выступать в качестве основного органа для своих пользователей. Сегодня мы можем реализовать функциональный рабочий процесс, используя реализацию HTML5, предоставленную Mozilla, и сервер browserid.org в качестве Вторичного удостоверяющего центра для проверки адресов электронной почты.

Допустим, Марк хочет войти на сайт myfavoritebeer.org, используя адрес электронной почты [email protected]. Есть три шага:

1. Предоставление сертификата
Если адрес электронной почты используется впервые в качестве идентификатора, браузер отправляет Марка на страницу безопасной аутентификации в GMail. Затем браузер генерирует пару ключей (используя navigator.id.genKeyPair() ) и отправляет открытый ключ в GMail.

GMail отправляет обратно сертификат, подписанный пакет, содержащий адрес электронной почты, открытый ключ пользователя и дату истечения срока действия. Он подписан с помощью закрытого ключа Gmail и упакован как JSON Web Token (JWT), подписанный объект JSON .

Сертификат хранится в цепочке ключей браузера вместе с закрытым ключом (с помощью navigator.id.registerVerifiedEmail() ) и используется на следующем шаге.

Если провайдер электронной почты не поддерживает BrowserID изначально, электронная почта проверяется сервером browserid.org как вторичный орган. Как правило, вы получаете письмо со ссылкой для посещения, чтобы подтвердить вашу личность.

2. Утверждение утверждения
Браузер генерирует утверждение , пакет JWT, доказывающий, что Марку принадлежит [email protected]. Пакет содержит целевой сайт (myfavoritebeer.org), дату истечения срока действия и сертификат, выданный GMail. Все подписано секретным ключом Марка и отправлено на myfavoritebeer.org для проверки.

Если браузер изначально не поддерживает BrowserID, то операция выполняется с помощью резервного кода JavaScript, предоставленного Mozilla.

3. Подтверждение утверждения
Сайт myfavoritebeer.org получает и проверяет запрос Марка. Сначала он проверяет срок действия, затем извлекает открытый ключ GMail для проверки сертификата. Сертификат содержит открытый ключ Марка, который используется для проверки утверждения.

Этот шаг предполагает, что GMail поддерживает BrowserID и разделяет его открытые ключи. В настоящее время сертификат содержит свойство «выдано», указывающее на вторичный орган, который его сгенерировал.

Если все идет хорошо, Марк регистрируется на сайте до истечения срока действия подтверждения.

Добавление аутентификации Persona на ваш сайт

В следующем примере используется и расширяется информация и код из официальной статьи о быстрой установке Mozilla Persona для получения базовой, но полностью функциональной системы. Кнопки используют стили CSS, созданные для Mozilla Sawyer Hollenshead . Код для примера можно скачать с GitHub.

Сначала вот основной файл index.php :

 <?php // Check if the user is logged in $user = null; if (!empty($_COOKIE['auth'])) { $user = $_COOKIE['auth']; } ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>My Favorite Beer - Mozilla Persona Test Code</title> <link rel="stylesheet" href="css/persona/persona-buttons.css"> </head> <body> <h1>My Favorite Beer: a test page for Mozilla Persona</h1> <?php if (!empty($user)) { ?> <p>Hello <strong><?=$user?></strong>!</p> <a href="#" class="persona-button persona-signout"><span>Sign out</span></a> <?php } else { ?> <a href="#" class="persona-button persona-signin"><span>Sign in with Persona</span></a> <?php } ?> <!--jQuery Library from Google--> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script> <!--Persona Library from Mozilla--> <script src="https://login.persona.org/include.js"></script> <!-- Our custom code --> <script src="js/mfb.js"></script> </body> </html> 

Я храню информацию об аутентификации (только адрес электронной почты) в переменной cookie с именем auth . Первые несколько строк PHP проверяют этот cookie и соответственно устанавливают локальную переменную $ user.

Переменная $user отмечена в основном тексте: если пользователь вошел в систему, появится сообщение с приветствием и кнопка «Выйти». Если нет, кнопка «Войти» отображается по умолчанию.

Внизу страницы вы можете найти библиотеку jQuery, mfb.js библиотеку Mozilla Persona и наш специальный файл JavaScript-кода mfb.js В этом файле я установил наши пользовательские хуки, которые связывают бэкэнд с API Persona.

 (function(window, $, undefined) { // See: http://www.quirksmode.org/js/cookies.html window.readCookie = function(name) { var nameEQ = name + '='; var ca = document.cookie.split(';'); for(var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == ' ') { c = c.substring(1, c.length); } if (c.indexOf(nameEQ) == 0) { return c.substring(nameEQ.length, c.length); } } return null; }; // Read auth info (the email address) from cookie var currentUser = window.readCookie('auth'); // If the user is not logged in set the default to null if (!currentUser) { currentUser = null; } // The returned value must be URL-decoded if (currentUser != null) { currentUser = decodeURIComponent(currentUser); } navigator.id.watch({ loggedInUser: currentUser, onlogin: function(assertion) { // A user has logged in! Here you need to send the // assertion to your backend for verification and to // create a session and then update your UI. $.ajax({ type: 'POST', url: 'login.php', // This is a URL on your website. data: {assertion: assertion}, success: function(res, status, xhr) { window.location.reload(); }, error: function(xhr, status, err) { alert('Login failure: ' + err); } }); }, onlogout: function() { // A user has logged out! Here you need to tear down the // user's session by redirecting the user or making a call // to your backend. Also, make sure loggedInUser will get // set to null on the next page load. $.ajax({ type: 'POST', url: 'logout.php', // This is a URL on your website. success: function(res, status, xhr) { window.location.reload(); }, error: function(xhr, status, err) { alert('Logout failure: ' + err); } }); } }); $(document).ready(function(){ $('a.persona-signin').click(function(e) { navigator.id.request(); e.preventDefault(); }); $('a.persona-signout').click(function(e) { navigator.id.logout(); e.preventDefault(); }); }); })(window, jQuery); 

Сначала есть функция быстрой утилиты, которая читает куки. Затем он пытается прочитать переменную currentUser из файла cookie. Он имеет URL-кодировку, поэтому нам нужна встроенная decodeURIComponent() для получения незашифрованного адреса электронной почты. Важно, чтобы эта переменная содержала либо действительный адрес электронной почты пользователя, либо нулевое значение JavaScript; любое другое значение вызовет бесконечный цикл перезагрузки.

Затем мы «наблюдаем» за необходимыми действиями onlogin и onlogout , вызывая API navigator.id.watch() и передавая детали нашего PHP-интерфейса. login.php и logout.php вызываются через Ajax POST этим API.

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

Сценарий входа отвечает за проверку личности пользователя у провайдера электронной почты. В реальном приложении мы также проверяем личность пользователя в базе данных приложения.

Основным потоком является:

 <?php // Call the BrowserID API $response = PersonaVerify(); // If the authentication is successful set the auth cookie $result = json_decode($response, true); if ('okay' == $result['status']) { $email = $result['email']; setcookie('auth', $email); } // Print the response to the Ajax script echo $response; 

Сначала он вызывает служебную функцию, которая инкапсулирует всю бизнес-логику (мы скоро это увидим). Если проверка идентичности положительна, то устанавливается файл cookie авторизации, и здесь должен вызываться другой код после проверки подлинности; в любом случае необработанный ответ JSON возвращается вызывающему сценарию.

Функция PersonaVerify() отвечает за подтверждение и проверку сертификата. Важно, чтобы этот шаг выполнялся на стороне сервера, чтобы ограничить возможность внедрения вредоносного кода. Обычно в этой функции мы должны извлечь открытый ключ из центра идентификации и проверить его самостоятельно, но поскольку спецификации все еще находятся в стадии разработки, а протокол BrowserID не реализован провайдерами электронной почты, безопасный способ сделать это — выполнить удаленную проверку с Persona служба проверки.

 <?php function PersonaVerify() { $url = 'https://verifier.login.persona.org/verify'; $assert = filter_input( INPUT_POST, 'assertion', FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW|FILTER_FLAG_STRIP_HIGH ); $scheme = 'http'; if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != "on") { $scheme = 'https'; } $audience = sprintf( '%s://%s:%s', $scheme, $_SERVER['HTTP_HOST'], $_SERVER['SERVER_PORT'] ); $params = 'assertion=' . urlencode($assert) . '&audience=' . urlencode($audience); $ch = curl_init(); $options = array( CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => 2, CURLOPT_POSTFIELDS => $params ); curl_setopt_array($ch, $options); $result = curl_exec($ch); curl_close($ch); return $result; } 

Код для этой функции взят из области разработчиков Mozilla Persona.

Параметр assertion берется из переменных Ajax POSTed и очищается. Параметр audience — это, по сути, URL нашего сайта в схеме формата: // host: port, поэтому он рассчитывается с использованием суперглобального $ _SERVER.

Оба параметра кодируются и отправляются с помощью вызова cURL HTTP POST в службу проверки личности. В результате получается строка JSON, которая возвращается в вызывающий скрипт для анализа.

Резюме

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

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

Вот и все пока, счастливого кодирования!

Изображение через Fotolia