Как работает OAuth 2.0 и OpenID Connect

OAuth 2.0 — протокол авторизации, позволяющий выдать одному сервису (приложению) права на доступ к ресурсам пользователя на другом сервисе. Протокол избавляет от необходимости доверять приложению логин и пароль, а также позволяет выдавать ограниченный набор прав, а не все сразу.

· 7 минуты на чтение
Как работает OAuth 2.0 и OpenID Connect

В этой статье рассмотрим историю возникновения и схему работы. Разберемся в чем отличие OAuth 2.0 от OpenID Connect и что такое SSO.

Спонсор поста

История возникновения OAuth

Авторизацией через социальные сети никого уже не удивишь. Нажимаешь кнопку соц сети, вжух и ты авторизовался на новом сайте. Сайт получил твоё ФИО, фотку и прочие данные. Но так было не всегда.

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

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

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

🙅‍♂️
OAuth 1.0 не используется. Забудьте о нем. Используйте OAuth 2.0

Главным недостатком OAuth 1.0 была слишком большая сложность данной версии.

Начнем разбор OAuth 2.0 с ролей. Всего есть 4 роли:

  • Владелец ресурса.
  • Клиент.
  • Сервер ресурсов.
  • Авторизационный сервер.

Далее мы рассмотрим каждую из ролей.

Владелец ресурса
Ресурсом являются данные, например ФИО, фотография, ваши сообщения в соц сетях, и прочее. Владелец ресурса это пользователь. При межсерверном общении владельцем ресурса может быть один из серверов.

Сервер ресурсов
На сервере ресурсов лежат ваши данные. В случае с примером выше ваши контакты Gmail это ресурс, а лежат они на серверах Gmail.

Клиент
Клиент это сервис, которому требуется доступ к вашим ресурсам. Например, Facebook требуется доступ к контактам Gmail.

Авторизационный сервер
В данном примере он принадлежит Google, так как он хранит ваши данные.

Стандарт не запрещает вам объединить Сервер ресурсов и Авторизационный сервер

Базовая схема протокола

OAuth 2.0 основан на использовании базовых веб-технологий: HTTP-запросах, редиректах и т. п. Поэтому использование OAuth возможно на любой платформе с доступом к интернету и браузеру: на сайтах, в мобильных и desktop-приложениях, плагинах для браузеров.

Вернемся к нашему примеру про Facebook и Gmail. На анимации ниже, я постарался схематично изобразить, как реализовать этот пример правильно с помощью Oauth2. Стоит учитывать, что у Google есть свой сервер авторизации, который отвечает за авторизацию на любом сервисе Google. Поэтому Gmail только хранит ресурсы, но не отвечает за авторизацию.

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

0:00
/
Access Token

Это строка, которая является альтернативой логину и паролю.

Особенности Access Token:

  • Ограниченное время жизни.
  • Его можно отозвать. Если есть подозрение, что токен украли, то мы просто запрещаем получать данные с помощью этого токена.
  • Компрометация токена не значит компрометирование логина и пароля. Из токена никак не получить логин и пароль.
  • По токену может быть доступна только часть данных (scope). Клиент запрашивает список разрешений, которые необходимы ему для работы, а Владелец ресурсов решает выдать такие права или нет. Например, можно выдать права только на просмотр списка контактов, тогда читать письма или редактировать контакты будет нельзя.

Помимо access_token Авторизационный сервер может выдавать также refresh_token. Это токен, который позволяет запросить новый access_token без участия Владельца ресурсов. Время жизни у такого токена намного больше access_token и его потеря гораздо серьезнее.

Вернемся к базовой схеме. Авторизационный сервер должен знать про каждого клиента, который делает к нему запрос. То есть, каждый клиент должен быть зарегистрирован. Зарегистрировав клиента мы получаем client_id и client_secret и обязаны передавать, как минимум client_id в каждом запросе.

Не все клиенты могут гарантировать сохранность client_secret, поэтому он есть не у всех. Например, SPA без бэкенда, теоретически достать оттуда можно что угодно.

Существует возможность регистрировать клиентов динамически: RFC 7591 и RFC 7592.

Рандомный блок

Способы получения Access Token

Всего есть 4 способа:

  • По авторизационному коду (Authorization Code Grant). Самый сложный и самый надежный способ.
  • Неявно (Implicit)
  • По логину и паролю пользователя (Resource Owner Password Credential). Только для безопасных клиентов, заслуживающих полного доверия.
  • По данным клиента (Client Credentials). Получаем токен по client_id и client_secret.

Client Credentials

Начнем разбор с самой простой схемы. Этот способ придуман для межсерверного взаимодействия. У нас есть два сервера API1 и API2, и им надо как-то общаться.

Client Credentials Scheme
  1. API 1 идет в авторизационный сервер передает туда client_id и client_secret.
curl --request POST \
 --url 'https://YOUR_DOMAIN/oauth/token' \
 --header 'content-type: application/x-www-form-urlencoded' \
 --data grant_type=client_credentials \
 --data client_id=YOUR_CLIENT_ID \
 --data client_secret=YOUR_CLIENT_SECRET \
 --data audience=YOUR_API_IDENTIFIER

2. Взамен API 1 получает access_token, с помощью которого может обратиться к API 2.

{
    "access_token": "eyJz93a...k4laUWw",
    "token_type": "Bearer",
    "expires_in": 86400
}

3. API 1 обращается к API 2.

curl --request GET \
--url https://api2.com/api \
--header 'authorization: Bearer ACCESS_TOKEN' \
--header 'content-type: application/json'

4. API 2 получает запрос с access_token и обращается к авторизационному серверу для проверки действительности переданного токена (RFC 7662).

Resource Owner Password Credential

🙅‍♂️
Эта схема не рекомендуется к использованию! В стандарте так и написано, если вы никакие другие схемы не можете сделать, то используйте эту.
  1. Владелец ресурсов передает свой логин и пароль Клиенту.
  2. Клиент отправляет Авторизационному серверу логин и пароль клиента, а так же свой client_id и client_secret.
curl -d "grant_type=password" \
    -d "client_id=3MVG9QDx8IKCsXTFM0o9aE3KfEwsZLvRt" \
    -d "client_secret=4826278391389087694" \
    -d "username=ryan%40ryguy.com" \
    -d "password=_userspassword__userssecuritytoken_" \
    https://as.com/oauth2/token

3. Если предоставленные пользователем учетные данные успешно аутентифицированы, сервер авторизации вернет ответ application/json, содержащий access_token:

{
    "id":"https://as.com/id/00DU0000000Io8rMAC/005U0000000hMDCIA2",
    "issued_at":"1316990706988",
    "instance_url":"https://na12.salesforce.com",
    "signature":"Q2KTt8Ez5dwJ4Adu6QttAhCxbEP3HyfaTUXoNI=",
    "access_token":"00DU0000000Io8r!AQcKbNiJPt0OCSAvxU2SBjVGP6hW0mfmKH07QiPEGIX"
}

Authorization Code Grant

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

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

0:00
/
  1. Пользователь на сайте нажимает кнопку авторизации, например через Facebook.
  2. Происходит редирект на авторизационный сервер.
  3. Если активной сессии нет, то Пользователь должен залогиниться. Если активная сессия есть, то просто подтвердить авторизацию.

Пример авторизационного запроса

https://YOUR_DOMAIN/authorize
     ?response_type=code
     &client_id=YOUR_CLIENT_ID
     &redirect_uri=https://YOUR_APP/callback
     &scope=SCOPE
     &audience=API_AUDIENCE
     &state=STATE
  • response_type - Обозначает тип учетных данных, которые возвращает авторизационный сервер. Для этого способа значение должно быть code.
  • redirect_uri - URL-адрес, на который авторизационный сервер будет перенаправлять браузер после авторизации пользователя. Код авторизации будет доступен в параметре code.

    В настройках Авторизационного сервера можно настроить url адреса, на которые разрешен редирект.

4. Если все пойдет хорошо, вы получите ответ HTTP 302. Код авторизации включен в конце URL-адреса: в параметре ?code=SplewFEFofer

HTTP/1.1 302
Found Location: https://YOUR_APP/callback?code=AUTHORIZATION_CODE&state=xyzABC123

Так как code попадает в браузер и ничем не защищен, то это точка уязвимости. Поэтому на авторизационный код накладываются следующие ограничения:

  • Код одноразовый
  • Время жизни кода очень мало

5. Теперь, когда у вас есть код авторизации, вы должны обменять его на токены. Используя извлеченный код авторизации из предыдущего шага, вам нужно будет выполнить POST на URL-адрес токена.

curl --request POST \
    --url 'https://YOUR_DOMAIN/oauth/token' \
    --header 'content-type: application/x-www-form-urlencoded' \
    --data 'grant_type=authorization_code' \
    --data 'client_id=YOUR_CLIENT_ID' \
    --data 'client_secret=YOUR_CLIENT_SECRET' \
    --data 'code=YOUR_AUTHORIZATION_CODE' \
    --data 'redirect_uri=https://YOUR_APP/callback'

6. Если все пойдет хорошо, вы получите ответ HTTP 200 с полезной нагрузкой, содержащей значения access_token, refresh_token, id_token и token_type:

{
    "access_token": "eyJz93a...k4laUWw",
    "refresh_token": "GEbRxBN...edjnXbL",
    "id_token": "eyJ0XAi...4faeEoQ",
    "token_type": "Bearer"
}

7. Чтобы вызвать ваш API из обычного веб-приложения, приложение должно передать полученный токен доступа в заголовке авторизации вашего HTTP-запроса.

curl --request GET \
    --url https://myapi.com/api \
    --header 'authorization: Bearer ACCESS_TOKEN' \
    --header 'content-type: application/json'

Implicit Grant

Теперь у нас сайт без бэкенда - SPA.

  1. Пользователь на сайте жмет на кнопку, происходит редирект на сервер авторизации.
  2. Пользователь вводит логин пароль, если он не авторизован.
  3. Происходит редирект обратно, но access_token возвращается в URL сразу: https://domain.com/back_path#access_token=8ijfwWEFFf0wefOofreW6rglk.

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

При использовании схемы Code Grant вы передаете токен в куке. Куки передаются в заголовке запроса. При использовании https соединения, заголовки шифруются. Поэтому достать токен из куки при передаче невозможно.

OpenID Connect

OAuth 2.0 разработан только для авторизации — для предоставления доступа к данным и функциям от одного приложения другому. OpenID Connect (OIDC) — это тонкий слой поверх OAuth 2.0, добавляющий сведения о логине и профиле пользователя, который вошел в учетную запись.

OpenID Connect позволяет реализовывать сценарии, когда единственный логин можно использовать во множестве приложений, — этот подход также известен как single sign-on (SSO)

Поток (flow) OpenID Connect выглядит так же, как и в случае OAuth 2.0. Единственная разница в том, что в первичном запросе используемый конкретный scope — openid, — а Клиент в итоге получает как access_token, так и id_token.

ID Token — это особым образом отформатированная строка символов - JSON Web Token или JWT. Сторонним наблюдателям JWT может показаться непонятной абракадаброй, однако Клиент может извлечь из JWT различную информацию, такую как ID, имя пользователя, время входа в учетную запись, срок окончания действия ID Token’а, наличие попыток вмешательства в JWT.

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

В случае OIDC также имеется стандартный способ, с помощью которого Клиент может запросить дополнительную информацию о пользователе от Сервера авторизации, например, адрес электронной почты, используя access_token.

Заключение

Подведем итог. OAuth 2.0 это простой протокол авторизации, основанный на HTTP, что дает возможность применять его практически на любой платформе. Он имеет хорошую документацию, и большинство крупных площадок его поддерживают. Так что если вы решили использовать этот протокол в своем проекте — это хороший выбор.

Struchkov Mark
Struchkov Mark
Задавайте вопросы, если что-то осталось не понятным👇