Современные стандарты идентификации: OAuth 2.0, OpenID Connect, WebAuthn
Пускать или не пускать? Вот в чем вопрос…
Сейчас на многих сайтах мы видим возможность зарегистрироваться или войти с помощью соцсетей, а некоторые сайты предлагают использовать внешние ключи безопасности или отпечатки пальцев. Что это? Стандарты с хорошо проработанной безопасностью или проприетарные реализации? Можем ли мы доверять этим технологиям и использовать их для разработки сайтов и в повседневной жизни? Давайте разбираться. Итак, сейчас существуют несколько стандартов и технологий для идентификации пользователей OAuth 2.0, OpenID Connect, WebAuthn, SAML 2.0, Credential Management API и др. В статье я расскажу о трех наиболее перспективных протоколах OAuth 2.0, OpenID Connect и WebAuthn. И чтобы понять как их применять на практике, сделаем три лабораторные работы. В качестве платформ для идентификации пользователей будем использовать GitHub и Google, на которых у большинства есть аккаунты.
OAuth 2.0
Начнем с самого известного протокола OAuth 2.0. Он был опубликован в 2012 году, как RFC 6749: The OAuth 2.0 Authorization Framework.
С помощью OAuth 2.0 пользователь разрешает определенному сайту получить свои закрытые данные из соцсетей, но без передачи сайту своих логинов / паролей. Например, когда вы регистрируетесь на сайте через Facebook, то как раз и предоставляете этому сайту разрешение получить из Facebook ваше имя, e-mail адрес и другие закрытые данные.
Давайте разберемся с технической реализацией. Для для простоты я буду называть любой сайт, который хранит идентификационные данные пользователя Соцсетью. А MySite буду называть любой сайт или приложение, которое хочет получить данные пользователя из Соцсети.
Стандарт определяет следующие роли:
- Resource Owner — пользователь, который заходит на MySite и дает ему разрешение использовать свои закрытые данные из Соцсети.
- Client (он же MySite) — приложение или интернет сайт, которым пользуется пользователь и которое взаимодействует с Authorization Server и Resource Server для получения закрытых данных пользователя.
- Authorization Server — сервер который проверяет логин/пароль пользователя, он же Соцсеть.
- Resource Server — хранит закрытую пользовательскую информацию, которую можно получить с помощью API. Authorization Server и Resource Server могут быть совмещены в одну систему.
Authorization flow
- Перед началом авторизации необходимо зарегистрировать MySite в Соцсети:
- Разработчик MySite задает Name (имя приложения), Homepage (адрес домашней страницы MySite) и Callback (адрес, на который Соцсеть перенаправит пользователя после успешной авторизации)
- Соцсеть выдает Client ID (иногда его называют AppID) и Client Secret.
- Client ID и Client Secret разработчик должен прописать в своем Client.
Теперь сам процесс. Детали конкретных реализаций могут различаться, но общая логика будет всегда следующая:
- Resource Owner заходит на Client (MySite), выбирает опцию «войти с помощью Соцсети», сайт перенаправляет пользователя в Cоцсеть на Authorization Server.
- Authorization Server проверяет есть ли у пользователя активная сессия и, если нет, то показывает форму для логина.
- Resource Owner вводит свои логин/пароль и подтверждает, что определенные закрытые данные могут быть использованы MySite, например имя пользователя или e-mail адрес.
- Authorization Server проверяет пользователя и перенаправляет на адрес Callback с результатом аутентификации и «Authorization Code»
- В ответ Client посылает «Authorization Code», Client ID и Client Secret.
- Authorization Server проверяет присланные данные и формирует «access token» в формате JWT (JSON Web Token), подписанный своим приватным ключем. В этом же JWT может содержаться и «refresh token», c помощью которого возможно восстановление сессии после ее окончания.
- После этого Client может запросить закрытую информацию пользователя с помощью вызова API, в который передается «access token».
- Resource Server проверяет «access token» (например, используя открытый ключ Authorization Server) и предоставляет доступ к данным пользователя.
OAuth 2.0 Лабораторная работа (GitHub)
Существует множество инструкций, как реализовать OAuth 2.0 авторизацию, используя соцсети. Мне лично понравилась следующая статья: Authentication using GitHub OAuth 2.0 with NodeJS В ней подробно описаны шаги и приведена тестовая программа. Но, чтобы действительно осознать алгоритм, лучше пройти все шаги руками (http запросами из браузера или вызовами curl). Поехали.
Для начала регистрируем свое приложение на GitHub: github.com/settings/applications/new
Задаем параметры:
Чтобы авторизация работала в рамках собственного сайта адреса должны быть реальными, но для лабораторной работы это не обязательно.
Получаем от GitHub:
- Client ID: ab8ec08a620c2
- Client Secret: e6fdd52b0a99e8fbe76b05c1b7bb93c1e
Разумеется во всех лабораторных работах все значения фейковые.
Так выглядит получение Client ID и Secret на сайте GitHub:
Теперь начинаем авторизацию. Считаем, что Шаг 1 уже пройден: пользователь зашел на MySite и выбрал «Войти с помощью GitHub ». Шаг 2 из call flow — это вызов в REST формате:
https://github.com/login/oauth/authorize?client_id=ab8ec08a620c2
- адрес — это точка логина на GitHub
- client_id — это Client ID, выданный при регистрации
В результате этого вызова GitHub показывает окно для авторизации:
Шаг 3: Вводим логин/пароль для доступа на GitHub
Шаг 4: GitHub перенаправляет запрос на Homepage, в этом запросе мы видим code:
http://MySite/home?code=a29b348f63d21
По данному адресу нет работающего сайта, но для нас главное знать присланный code, чтобы сформировать следующий Шаг 5:
https://github.com/login/oauth/access_token?client_id=ab8ec08a620c2&
client_secret=e6fdd52b0a99e8fbe76b05c1b7bb93c1e&
code=a29b348f63d21
- адрес — это точка получения access token на GitHub
- client_id — это Client ID, выданный при регистрации
- client_secret это Client Secret, выданный при регистрации
- code — это только что присланный code
Шаг 6: В ответ получили access token:
access_token=31b71cbd372acdbb20ec1644b824f3dd0&scope=&token_type=bearer
Шаг 7: Вставляем access_token в заголовок запроса и вызоваем GitHub API:
curl -H "Authorization: token 31b71cbd372acdbb20ec1644b824f3dd0" https://api.github.com/user
Шаг 8: В ответ получаем JSON с полезной информацией обо мне, которую можно использовать для формирования профиля на MySite:
{
"login": "AlexeySushkov",
"html_url": "https://github.com/AlexeySushkov",
"repos_url": "https://api.github.com/users/AlexeySushkov/repos",
"name": "Alexey Sushkov",
"blog": "http://sushkov.ru",
"location": "St.Petersburg, Russia",
"email": "alexey.p.sushkov@gmail.com",
и т.д.
}
На самой деле, мы рассмотрели только один сценарий работы OAuth 2.0. Существует несколько сценариев, каждый из которых используется в зависимости от приложения, соображений безопасности, способу развертывания и т.д. Описание всех сценариев можно найти, например, тут: OAuth 2.0 in a Nutshell.
OpenID Connect
С OAuth 2.0 немного разобрались. Теперь выясним зачем нужен OpenID Connect, который является надстройкой над OAuth 2.0:
- C помощью OAuth 2.0 мы только авторизовали пользователя т.е. о пользователе мы знаем только access token, с помощью которого можем получать определенные данные из Соцсети. Но access token ничего не говорит о личности пользователя и с помощью него мы не можем предоставить доступ к закрытым данным пользователя на нашем сайте MySite. OpenID Connect — добавляет сведения о логине и профиле пользователя (identity). Это как раз и позволяет реализовать его аутентификацию.
- OpenID Connect также добавляет возможность динамической регистрации и обнаружения сервисов «service discovery». Это дает возможность строить системы SSO (Single Sign-On), в которых используется один логин для многих не связанных между собой сервисов.
Давайте посмотрим на стандарт с технической стороны.
OpenID Connect (OIDC) — открытый стандарта OpenID, который разрабатывает консорциум OpenID Foundation. OIDC расширяет OAuth 2.0 следующими основными возможностями:
- Authorization Server, помимо access token и refresh token, возвращает «identity token» (ID Token). Он содержится в том же JWT. Из ID Token можно извлечь следующую информацию: имя пользователя, время входа в учетную запись, срок окончания действия ID Token. ID Token можно передавать между участниками.
- OIDC предоставляет дополнительное API, которые позволяет запрашивать информацию о пользователе и его текущих сессиях.
Диаграмма взаимодействия в OpenID Connect выглядит так же, как и в случае OAuth. Единственная разница в содержимом запросов:
- В первичном запросе на получение code добавляется дополнительный атрибут scope=openid.
- В результате работы алгоритма Client, помимо access и refresh token, получает ID Token.
OpenID Connect: Лабораторная работа (Google)
Теперь посмотрим, чем нас по данной теме порадует Google. Есть подробная инструкция по конфигурации и использованию OpenID Connect от Google и «песочница» по использованию Google API: Google OAuth 2.0 Playground.
Здесь, как и в случае с OAuth 2.0, мы пройдем путь по шагам и посмотрим на приходящие данные. Аналогично считаем, что приложение зарегистрировано, Client ID и Client Secret получены, Шаг 1 пройден. Шаг 2 из call flow — это вызов в REST формате:
https://accounts.google.com/o/oauth2/v2/auth?
response_type=code&
client_id=140797064495-b8b79j42m97nkkrlndfstikv8.apps.googleusercontent.com&
scope=openid%20email&
redirect_uri=http%3A//c18529.shared.hc.ru/wp-login.php&
state=765439764
У Google немного посложнее, т.к. безопасности они уделяют больше внимания:
- адрес — это точка логина на Google
- response_type=code — ожидаем получить в ответ code
- client_id — Client ID, выданный при регистрации
- scope=openid email — к каким данным мы хотим получить доступ
- redirect_uri — redirect_uri заданный при регистрации приложения
- state — сгенерированное Client-ом число, которое передается между Client и AS для защиты от вмешательства злоумышленника.
Шаг 3: Форма ввода пароля отсутствует, т.к. я уже был залогинен в Google.
Шаг 4: Google перенаправляет запрос на Homepage, в этом запросе мы видим code:
http://MySite?state=123&code=4/xAFkcMzhyJhUErRJYwIyntSYN-WeJjfZHLiwWL4IaT-WkHzMU18xABlPmev-M_87wVbqTkQ1y93w6GB5&scope=email+openid+https://www.googleapis.com/auth/userinfo.email&authuser=0&prompt=none
Также как и в случае с GitHub по данному адресу нет работающего сайта, но это и не надо, нам главное знать code, чтобы сформировать следующий Шаг 5. Тут тоже немного посложнее, т.к. Google требует запрос POST, а не GET:
curl -d "code=4/xAFkcMzhyJhUErRJYwIyntSYN-WeJjfZHLiwWL4IaT-WkHzMU18xABlPmev-M_87wVbqTkQ1y93w6GB5&client_id=140797064495-b8b79j42m97nkkrlndfstikv8.apps.googleusercontent.com&
client_secret=HMVctrTicW6RC1Q8T&
redirect_uri=http%3A//c18529.shared.hc.ru/wp-login.php&
grant_type=authorization_code"
-H "Content-Type: application/x-www-form-urlencoded" -X POST https://oauth2.googleapis.com/token
- адрес — это точка получения token на Google
- code — это только что присланный code
- client_id — это Client ID, выданный при регистрации
- client_secret это Client Secret, выданный при регистрации
- grant_type=authorization_code — единственно правильное значение из стандарта
Шаг 6: В ответ получили access_token и id_token:
{
"access_token": "ya29.Il_AB0KeKnjBJx0dhjm2nCLt1B-Mq0aQBW5T302JnlZfsxW1AXqLFfDJZRMi2R2WKG4OX4msKLjx4E4CSl4G_4ajAy3aqrf4pM0ic0jJr092pA67H9aktJktCbx",
"expires_in": 3327,
"scope": "openid https://www.googleapis.com/auth/userinfo.email",
"token_type": "Bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE3ZDU1ZmY0ZTEwOTkxZDZiMGVmZDM5MmI5MWEzM2U1
………………………………много_букв…………………………………………………….._4mUTiMNSAHljap1hLD2hAzgOZWuQ"
}
Что теперь делать с этим богатством?
Шаг 7: C access_token все понятно: включаем его в вызов API, например GMail:
curl -H "Authorization: Bearer ya29.a0Adw1xeWvFoxHKNICHnV6vFFj5TZdPQVlYD98h8wjW95ZEbHVui_pk7HGRoq3Q7MlVLV23xkVM0yyjSP8ClSlvfUy3b_IqvKQW5Lvwj38QzJhee-aH1grerB4pRpMzn_FGueigG_RGI56pKPgFBTr49cpynQy" https://www.googleapis.com/gmail/v1/users/alexey.p.sushkov@gmail.com/profile
Шаг 8: В ответ получаем JSON с полезной информацией:
{
"emailAddress": "alexey.p.sushkov@gmail.com",
"messagesTotal": 372543,
...
}
Теперь давайте проверим утверждение, что в id_token содержится информация для аутентификации пользователя и поддержания сессии. Для этого надо расшифровать содержимое. Самый простой способ сделать это, обратиться к Google API по адресу
oauth2.googleapis.com/tokeninfo и в качестве параметра указать полученный id_token:
https://oauth2.googleapis.com/tokeninfo?id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjE3ZDU1ZmY0ZTEwOTkxZDZiMGVmZDM5MmI5MWEzM2U1NGMwZTIxOGIiLCJ0eXAiOi
………………………много_букв……………………………...
SVQ5GQni3irfOzOXYEiqijp6TjGa_a-3jKcEsU5TbasZmAIejsdVcNy2_4mUTiMNSAHljap1hLD2hAzgOZWuQ
Получили JSON:
{
"iss": "https://accounts.google.com",
"email": "alexey.p.sushkov@gmail.com",
"email_verified": "true",
"iat": "1583257010",
"exp": "1583260610",
"typ": "JWT"
...
}
Видим, что в id_token содержится информация о логине пользователя, времени получения и времени жизни токена. Можно сделать вывод, что OpenID Connect от Google работает, и его можно применять для соответствующих сценариев.
WebAuthn
Web Authentication API (also known as WebAuthn):
- Стандарт дает пользователям возможность идентифицироваться на сайтах и в приложениях с помощью внешних ключей безопасности (например, USB-ключей) или по отпечатку пальца и, впоследствии, по другим биометрическим данным: лицу, сетчатке глаза.
- Основная задача стандарта — избавить пользователей от логинов / паролей и перейти на идентификацию с помощью криптографии с использованием публичных ключей «Public key cryptography». Public key cryptography — это криптографическая концепция, использующая пары математически связанных ключей. Private key (закрытый ключ) хранится в безопасном месте у пользователя, а public key (открытый ключ) хранится и используется открыто.
- Над стандартом работют организации W3C (World Wide Web Consortium) и FIDO, при участии Google, Mozilla, Microsoft, Yubico. W3C дал миру стандарты HTTP, HTML, XML и многие другие. Наличие таких серьезных игроков говорит о большом потенциале стандарта WebAuthn. WebAuthn поддерживается в следующих браузерах: Chrome, Firefox, Edge, Safari.
Роли
По сравнению с OAuth 2.0 в WebAuthn добавляются роли:
Authenticator: внешний ключ безопасности (физический носитель или сканер отпечатков пальцев), который позволяет аутентифицировать пользователя, использую различные технологии, такие как BlueTooth/NFC/USB. Служит для:
- Генерации public key credentials (пар открытых / закрытых ключей).
- Authenticator безопасно хранит закрытый ключ в своей памяти
- Передает открытый ключ внешним системам
- Подписывает данные закрытым ключом и результат передает внешним системам
Для взаимодействия с браузером Authenticator использует протокол CTAP (Client to Authenticator Protocols).
Relying Party: выполняет ту же функцию, что и «Authorization Server» в OAuth 2.0, а именно проверяет идентификационные данные пользователя. Только в OAuth 2.0 это были логин/пароль, а в WenAuthn — public key credentials.
User Agent: объединяет браузер и сетевое приложение, служит для тех же целей, что и Client в OAuth 2.0, а именно — с одной стороны взаимодействует с пользователем и предоставляет ему GUI, а с другой стороны взаимодействует с системами, в которых хранятся идентификационные данные пользователя.
Authorization flow
Перед началом процесса аутентификации, также как и в OAuth 2.0, надо совершить подготовительные действия, только в OAuth 2.0 мы регистрировали приложение, а в WebAuth 2.0 регистрируем пользователя. В Web Authentication API специфицировано два вызова:
- navigator.credentials.create — для создания идентификационных данных пользователя
- navigator.credentials.get — осуществляет проверку идентификационных данных пользователя
Соответственно, для регистрации надо вызвать navigator.credentials.create.
В результате этого в Authenticator-е пользователя сохранится закрытый ключ для определенного сайта, а в Relying Party сохранится открытый ключ.
После этого процесс аутентификации будет таким:
- Зарегистрированный пользователь заходит на сайт и в форме логина выбирает войти с помощью «WebAuthn». На сайте будет написано, конечно, не WebAuthn, а «войти по отпечатку пальца» или «войти с помощью внешнего ключа». Приложение делает на Relying Party запрос Challenge. Challenge — имеет тот же смысл, что и Code в OAuth 2.0.
- Relying Party присылает Challenge. Этот обмен не регламентируется стандартом, но обычно используется формат REST API.
- Взаимодействие браузера и Authenticator-а происходит по протоколу CTAP (Client to Authenticator Protocols). Браузер вызывает navigator.credentials.get c параметрами:
- Challenge
- Дополнительные атрибуты
- Authenticator ищет идентификационные данные пользователя, опционально запрашивает у пользователя подтвердить, что он это он.
- Если проверка прошла успешно, то Authenticator возвращает подписанные приватным ключом данные.
- Приложение посылает подписанные данные на Relying Party.
- Relying Party расшифровывает данные с помощью открытого ключа, проверяет Challenge и авторизует пользователя.
Для закрепления материала делаем лабораторную работу:
WebAuthn: Лабораторная работа (Google)
Для реализации WebAuthn только http запросами не обойтись, т.к. нужно вызывать браузерное API для взаимодействия с Authenticator-ом. Но и тут Google радует, сделали песочницу с пошаговой инструкцией: Your first WebAuthn.
В результате выполнения работы получится JS клиент-серверное приложение, которое реализует аутентификацию с помощью отпечатка пальца. Работающая дема находится по адресу.
Если ее запустить на смартфоне с датчиком отпечатков пальцев, то можно посмотреть результат работы. Как обычно, сначала подготовка — регистрируем пользователя:
Создаем логин / пароль и далее привязываем отпечаток пальца:
После этого программа показывает какой открытый ключ привязан к этому отпечатку:
Теперь можно начинать сценарий по аутентификации. Как обычно, считаем, что Шаг 1 пройден и мы находимся на сайте. Чтобы перейти к Шагу 2 надо нажать «Try Reauth». Браузер выполнит Шаг 3 и вступит во взаимодействие с Authenticator-ом, который на Шаге 4 попросит приложить палец:
И если приложен зарегистрированный палец, то Шаги 5 и 6 успешно пройдут и на Шаге 7 приложение опять покажет окно с соответствующим открытым ключом:
Заключение
Итак, мы рассмотрели три наиболее распространенных и перспективных протокола для идентификации пользователей: OAuth 2.0, OpenID Connect, WebAuthn. Поняли области их применимости:
- OAuth 2.0 — используется для регистрации и входа пользователей на сайты с помощью соцсетей. А также для получения данных пользователей из соцсетей.
- OpenID Connect — используется для аутентификации пользователей и позволят предоставить им доступ к своим закрытым данным на сайтах. Также OpenID Connect служит для реализации сложных сценариев взаимодействия в корпоративных SSO системах.
- WebAuthn — используется для добавления на сайт возможности аутентификации с помощью внешнего физического ключа или отпечатка пальца.
Выводы
- Очевидно, что современный сайт должен реализовать все возможные способы регистрации и авторизации, чтобы у пользователя был осознанный выбор.
- Появилась возможность никогда не хранить пароль пользователя, чтобы не компрометировать свою репутацию, а хранить только логины и зашифрованные личные данные.
- Имеет смысл отдать аутентификацию пользователей облачным платформам, таким как Facebook или Google, т.к. в них работают лучшие специалисты по безопасности, которые могут предусмотреть все нюансы безопасности.
- Предлагаю с оптимизмом смотреть в будущее, т.к. протокол WebAuthn — реальный шанс избавится от парольного ада нашего времени!
It’s only the beginning!
Приложение: остальные протоколы идентификации
Для полноты картины перечислю остальные актуальные протоколы и технологии, используемые для идентификации пользователей:
SAML 2.0 (Security Assertion Markup Language)
Зрелый протокол 2005 года, но имеет ограниченный набор сценариев для построения систем SSO. Использует формат данных на основе XML. Подробнее можно посмотреть в статье: «Кто использует протокол аутентификации SAML 2.0»
Credential Management API
Разработкой занимается та же организация, что и WebAuthn — W3C. Стандарт Credential Management позволяет:
- Хранить идентификационные данные абонентов, что позволяет пользователям заходить на сайты не вводя пароли, а использовать пароли из хранилища.
- Выбирать нужные аккаунты для входа на определенные сайты.
- Позволяет использовать логины /пароли, введенные на одном устройстве на других устройствах.
Пример реализации Credential Management API, известный всем — это диспетчер паролей в Google: passwords.google.com