В этой статье мы рассмотрим историю возникновения OAuth, его основные принципы работы и архитектуру. Поговорим о том, как протокол стал основой для безопасного взаимодействия между приложениями, не требующего передачи учётных данных. Кроме того, разберём ключевые различия между OAuth 2.0 и OpenID Connect (OIDC), чтобы понять, как OIDC дополняет возможности OAuth для обеспечения единой аутентификации (Single Sign-On, SSO). Мы также обсудим, какие сценарии применения лучше подходят для каждого из этих протоколов, и как обеспечить их безопасное использование в современных веб-приложениях.
История возникновения OAuth
Сегодня авторизация через социальные сети – привычное дело: нажимаешь кнопку соцсети, и мгновенно входишь на новый сайт, который получает доступ к вашему имени, фотографии и другим данным. Однако так было не всегда.
В «ранние времена» интернета всё было намного проще и одновременно менее безопасно. Пользователи просто передавали логин и пароль от одного сервиса другому, чтобы тот мог войти в их учётную запись и получить доступ к необходимой информации. Например, на заре становления, Facebook предлагал пользователям ввести логин и пароль от их аккаунта Gmail, чтобы отправить приглашения их контактам. Такой подход имел серьёзный недостаток: передавая свои данные, пользователи предоставляли сервису полный доступ к своей учётной записи.
Для решения этой проблемы и был разработан стандарт OAuth. С его помощью вы можете разрешить приложению доступ к вашим данным или функциям другого приложения от вашего имени, не раскрывая свой пароль. OAuth 2.0 делает этот процесс удобным и безопасным, предоставляя следующие ключевые преимущества:
- Повышенная безопасность. OAuth 2.0 позволяет предоставлять доступ к данным без необходимости передачи пароля, что защищает учётные данные пользователя от компрометации.
- Ограничение прав доступа. OAuth предоставляет доступ только к необходимым данным и функциям. Например, приложение может получить доступ к контактам, но не к личным сообщениям или файлам пользователя.
- Гибкость. OAuth 2.0 поддерживает различные типы разрешений, что позволяет адаптировать его под разные приложения (веб, мобильные и серверные) и сценарии доступа.
- Централизованное управление доступом. OAuth 2.0 даёт владельцу ресурса возможность управлять списком приложений, которым предоставлен доступ, и отзывать доступ в любой момент.
- Поддержка единого входа (SSO). OAuth 2.0 легко интегрируется с решениями SSO, такими как OpenID Connect, что позволяет пользователям использовать один аккаунт для доступа ко многим сервисам, упрощая процесс входа.
Основы Oauth 2.0
OAuth 2.0 основан на использовании базовых веб-технологий: HTTP-запросов, редиректов и т. п. Это делает его универсальным стандартом, который можно использовать на любой платформе с доступом к интернету и браузеру: на сайтах, в мобильных и десктопных приложениях, а также в браузерных плагинах.
Основные роли
Прежде чем рассмотреть сам процесс авторизации, важно понять основные роли, участвующие в схеме работы OAuth 2.0. Каждая из этих ролей выполняет свою задачу, обеспечивая безопасность и точность процесса доступа к данным.
Разделение на роли позволяет системе авторизации гибко контролировать доступ к защищённым ресурсам, минимизируя риски утечки информации. Рассмотрим эти роли на примере авторизации Facebook через Gmail:
- Владелец ресурса. Это пользователь, который владеет данными (такими как контакты в Gmail). Именно он решает, кому и в каком объёме предоставить доступ к своим данным. В некоторых случаях владельцем ресурса может быть не пользователь, а другой сервер, например, при взаимодействии серверов между собой.
- Клиент. Это устройство пользователя или сервис, которому требуется доступ к данным владельца ресурса. В нашем примере Facebook выступает клиентом, запрашивая доступ к контактам пользователя в Gmail для отправки приглашений.
- Сервер ресурсов. Это сервер, где хранятся защищённые данные владельца ресурса. В данном примере сервером ресурсов является сервер Gmail, на котором лежат контакты пользователя.
- Авторизационный сервер. Этот сервер отвечает за проверку прав доступа к данным. В нашем примере, авторизационный сервер – это сервер Google, который выполняет аутентификацию пользователя и выдаёт токен доступа (access token), подтверждающий разрешение на доступ к данным.
Базовая схема протокола
Теперь, когда роли понятны, вернёмся к примеру с Facebook и Gmail. На анимации ниже схематично показано, как правильно реализовать этот процесс с помощью OAuth 2.0. Google использует свой авторизационный сервер для проверки прав доступа на всех своих сервисах, включая Gmail, который в данном случае лишь хранит ресурсы, но не участвует в процессе авторизации.
Access Token
Ключевая цель процесса – предоставить клиенту (в нашем случае Facebook) доступ к данным, не раскрывая пароля. Клиент должен получить от авторизационного сервера специальный токен доступа (access token). Этот токен позволяет запрашивать данные у сервера ресурсов, но ограничивает доступ в соответствии с заданными разрешениями.
Особенности Access Token:
- Ограниченное время жизни. Access token имеет ограниченный срок действия для повышения безопасности.
- Возможность отзыва. Если есть подозрение, что токен скомпрометирован, его можно отозвать, заблокировав дальнейший доступ.
- Отдельность от пароля. Компрометация токена не означает утечку пароля, так как из токена невозможно получить логин и пароль пользователя.
- Ограниченные права доступа (scope). Access token может предоставить доступ только к определённым данным. Например, Facebook может запросить доступ только к списку контактов, но не к сообщениям.
Refresh Token
Помимо access token, авторизационный сервер может выдать клиенту и refresh token. Когда срок действия access token истекает, клиент отправляет запрос к авторизационному серверу, передавая refresh token. Авторизационный сервер проверяет его действительность и, если всё в порядке, выдаёт новый access token. Этот процесс позволяет сохранить сессию пользователя активной без необходимости повторной аутентификации.
Поскольку refresh token даёт возможность запрашивать новые access token, его потеря или компрометация представляет серьёзную угрозу. В случае утечки злоумышленник может получить долгосрочный доступ к данным. Поэтому рекомендуется хранить refresh token в защищённой среде, особенно в серверных приложениях.
Способы получения Access Token
Существует четыре основных способа получения access token в OAuth 2.0, каждый из которых подходит для определённых сценариев:
- Authorization Code Grant. Это самый безопасный и надёжный метод. Он предполагает обмен одноразового кода авторизации на access token через серверную часть приложения, что делает его менее уязвимым к перехвату токена.
- Implicit Grant. Более простой метод, используемый для клиентских приложений без серверной части, таких как SPA (Single Page Applications). access token передаётся напрямую через URL-фрагмент, что упрощает реализацию, но снижает безопасность, так как токен может быть доступен через JavaScript.
- Resource Owner Password Credentials. В этом методе пользователь передаёт свои логин и пароль напрямую клиенту, который затем запрашивает access token у сервера авторизации. Он подходит только для доверенных клиентов, так как пользователь делится своими учётными данными с клиентом, что создаёт риск утечки.
- Client Credentials. Используется для межсерверного взаимодействия, когда одно серверное приложение (клиент) запрашивает access token у авторизационного сервера с использованием client_id и client_secret. Этот метод подходит для сценариев, когда доступ к ресурсам необходим от имени самого клиента, а не пользователя.
Подробно рассмотрим каждый способ.
Client Credentials
Рассмотрим один из самых простых способов авторизации в OAuth 2.0 — схему Client Credentials. Этот метод разработан для безопасного взаимодействия между сервисами и часто используется, когда один сервис должен обращаться к другому сервису без участия пользователя.
Каждый клиент должен быть зарегистрирован на авторизационном сервере. В процессе регистрации клиент получает уникальные идентификаторы:
- client_id – это публичный идентификатор клиента, который передаётся в каждом запросе на авторизацию. Он позволяет авторизационному серверу определить, какое приложение запрашивает доступ.
- client_secret – это секретный ключ, который используется для проверки подлинности клиента. Он должен храниться в надёжном месте и не должен быть раскрыт третьим лицам.
Запрос токена (1). Service 1 отправляет запрос к авторизационному серверу, передавая свои client_id
и client_secret
. Это делается с использованием метода POST и указанием типа авторизации grant_type=client_credentials
:
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
Здесь audience
указывает целевой ресурс (Service 2), для которого будет действовать access_token
. Это поле определяет, какие именно ресурсы будут доступны по этому токену. Авторизационный сервер проверяет audience
при выдаче токена, чтобы убедиться, что запрос направлен на доступ к конкретному API. Это помогает избежать использования токена не по назначению и усиливает безопасность.
Получение access_token (2). В ответ на запрос авторизационный сервер выдаёт access_token
, который Service 1 может использовать для обращения к Service 2:
{
"access_token": "eyJz93a...k4laUWw",
"token_type": "Bearer",
"expires_in": 86400
}
access_token
: строка токена, которая используется для авторизации запросов к Service 2. Это временный токен доступа.token_type
: обычноBearer
, что указывает на способ передачи токена в заголовкеAuthorization
.expires_in
: время действия токена в секундах (например, 86400 секунд, что соответствует 24 часам). После этого срока действия токен становится недействительным.
Обращение к Service 2 (3). Service 1 использует полученный access_token
для отправки запросов к Service 2. Токен передаётся в заголовке Authorization
с типом Bearer
:
curl --request GET \
--url https://api2.com/api \
--header 'authorization: Bearer ACCESS_TOKEN' \
--header 'content-type: application/json'
Проверка токена (4). Получив запрос с access_token
, Service 2 должен убедиться, что токен действителен и не был отозван. Для этого существуют два подхода:
- Запрос к авторизационному серверу: Service 2 может обратиться к авторизационному серверу с помощью протокола, описанного в RFC 7662, чтобы проверить действительность токена. Авторизационный сервер может подтвердить, что токен не истёк и не был отозван.
- Локальная проверка с использованием JWT: Если
access_token
представлен в виде JWT (JSON Web Token), Service 2 может выполнить локальную проверку токена. JWT содержит в себе закодированную информацию о клиенте и правах доступа, и Service 2 может проверить подпись токена, чтобы убедиться в его подлинности без обращения к авторизационному серверу. Это снижает нагрузку на сервер и ускоряет обработку запросов.
Когда использовать Client Credentials?
Схема Client Credentials подходит для следующих сценариев:
- Межсерверное взаимодействие, где один сервер должен получить доступ к API другого сервера, например, для синхронизации данных.
- Микросервисная архитектура, где отдельные микросервисы взаимодействуют друг с другом через API.
- Сервисные учётные записи, когда доступ к API требуется не от имени конкретного пользователя, а от имени самого сервиса.
Важно учитывать, что client_secret должен храниться только в безопасных серверных окружениях. Это значит, что клиент должен быть доверительным и иметь возможность безопасно хранить client_secret без риска его утечки. Поэтому Client Credentials не рекомендуется использовать в клиентских приложениях, таких как мобильные или SPA, где client_secret может быть легко извлечён.
Resource Owner Password Credential
Эта схема авторизации является одной из самых простых, но при этом наименее безопасных. В самом стандарте OAuth 2.0 отмечено, что её использование не рекомендуется, и следует прибегать к ней только в том случае, если невозможно реализовать более безопасные схемы авторизации.
Схема Resource Owner Password Credential предполагает передачу пользователем своих учётных данных (логина и пароля) напрямую клиенту, что создаёт значительные риски для безопасности. Этот метод удобен, когда клиент и сервер авторизации находятся под контролем одной организации, однако он крайне уязвим при взаимодействии с третьими сторонами.
- Передача логина и пароля. Владелец ресурсов (пользователь) передаёт свои логин и пароль клиенту, доверяя ему обработку этих данных. Это предполагает, что клиент является безопасным и не будет злоупотреблять полученными учётными данными.
- Запрос токена клиентом. Клиент затем отправляет логин и пароль пользователя на авторизационный сервер вместе с
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
Если авторизационный сервер успешно аутентифицирует учётные данные пользователя, он возвращает 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
Authorization Code Grant – это рекомендуемый способ авторизации для OAuth 2.0, особенно в тех случаях, когда речь идёт о серверных приложениях. Он обеспечивает высокий уровень безопасности, поскольку позволяет хранить учётные данные клиента и секреты на серверной стороне, недоступной для посторонних.
Этот метод подходит для веб-приложений, где исходный код и секреты клиента остаются скрытыми от пользователей. Исключением для использования этого метода является межсерверное взаимодействие, где больше подходит схема Client Credentials.
Этот способ авторизации включает несколько этапов, обеспечивающих безопасный обмен данными между клиентом и сервером:
Пользователь на сайте нажимает кнопку авторизации (например, «Войти через Facebook»), инициируя процесс получения прав доступа. После этого происходит перенаправление пользователя на страницу авторизационного сервера, где он должен пройти аутентификацию.
Пользователь видит страницу авторизационного сервера:
- Если у него уже есть активная сессия на авторизационном сервере, то он просто подтверждает своё согласие на предоставление доступа.
- Если сессии нет, пользователь должен войти в свой аккаунт на авторизационном сервере.
После успешной аутентификации пользователь перенаправляется на URL-адрес, указанный клиентом в параметре redirect_uri
. Пример авторизационного запроса выглядит следующим образом:
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
. В настройках авторизационного сервера можно указать разрешённые адреса redirect_uri для предотвращения атак на переадресацию (redirect attacks).
Если авторизация прошла успешно, клиент получает код авторизации через HTTP-ответ 302, где в URL содержится параметр code:
HTTP/1.1 302
Found Location: https://YOUR_APP/callback?code=AUTHORIZATION_CODE&state=xyzABC123
code
) – это одноразовый короткоживущий код, который клиент должен использовать для обмена на access_token. Он отправляется через браузер, поэтому уязвим для атак.После получения кода авторизации, клиент отправляет POST-запрос к авторизационному серверу для обмена кода на access_token и другие токены:
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'
Здесь указывается grant_type=authorization_code
, который указывает на тип авторизации, и параметры client_id
, client_secret
, code
и redirect_uri
для идентификации клиента и подтверждения запроса.
Если обмен прошёл успешно, авторизационный сервер возвращает HTTP-ответ с полезной нагрузкой в формате JSON, содержащей различные токены:
{
"access_token": "eyJz93a...k4laUWw",
"refresh_token": "GEbRxBN...edjnXbL",
"id_token": "eyJ0XAi...4faeEoQ",
"token_type": "Bearer",
"expires_in": 3600
}
access_token
– используется для доступа к защищённым ресурсам.refresh_token
– позволяет запросить новый access_token после его истечения.id_token
– используется для передачи информации о пользователе (при использовании OpenID Connect).expires_in
– срок действия access_token в секундах.
После получения access_token клиент может использовать его для авторизации запросов к защищённым API, добавляя токен в заголовок Authorization:
curl --request GET \
--url https://myapi.com/api \
--header 'authorization: Bearer ACCESS_TOKEN' \
--header 'content-type: application/json'
API проверяет подлинность токена и предоставляет доступ к ресурсам в зависимости от разрешений (scope
), указанных при запросе авторизации.
Implicit Grant
Implicit Grant предназначен для приложений без серверной части, таких как SPA (Single Page Applications). Этот метод упрощает процесс получения access_token, но делает его менее безопасным по сравнению с другими схемами.
Принцип работы
Пользователь нажимает на кнопку авторизации на сайте (например, «Войти через Google»), что инициирует процесс авторизации. Затем пользователь перенаправляется на страницу авторизационного сервера.
Если у пользователя уже есть активная сессия, он просто подтверждает доступ. В противном случае ему нужно ввести свои логин и пароль на странице авторизационного сервера.
После успешной аутентификации пользователь перенаправляется обратно на сайт клиента, и access_token передаётся в виде фрагмента URL после символа #
:
https://domain.com/back_path#access_token=8ijfwWEFFf0wefOofreW6rglk
Хотя access_token
передаётся в URL, он остаётся доступен только в браузере и не отправляется на сервер при выполнении HTTP-запросов. Это связано с тем, что фрагмент предназначен исключительно для обработки на стороне клиента. JavaScript в браузере может прочитать фрагмент через window.location.hash
и извлечь токен:
const accessToken = window.location.hash.split('=')[1];
fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
Приложение использует этот токен для выполнения защищённых запросов к API, добавляя его в заголовок Authorization
. Благодаря использованию HTTPS соединение зашифровано, и сам токен не виден в сети
fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Authorization': 'Bearer 8ijfwWEFFf0wefOofreW6rglk'
}
});
Уязвимости передачи токена через URL
Из-за этих рисков использование Implicit Grant рекомендуется ограничивать, особенно в случаях, когда возможна реализация более защищённых методов авторизации
Доступность фрагмента в браузере. Хотя фрагменты URL не передаются на сервер, они доступны в JavaScript. Это делает токен уязвимым для межсайтового скриптинга (XSS), когда злонамеренные скрипты могут извлечь access_token из URL и передать его злоумышленнику
Сохранение в истории браузера и закладках. URL с access_token может быть случайно сохранён в истории браузера или закладках. Это создаёт риск утечки токена, если пользователь случайно поделится таким URL с другими или если на устройстве установлено вредоносное ПО, которое может получить доступ к истории браузера.
Неправильная настройка redirect_uri. Если redirect_uri не проверяется строго и злоумышленник указывает свой собственный URI, он может получить access_token. В этом случае пользователь будет перенаправлен на страницу, контролируемую злоумышленником, с токеном в URL-фрагменте, что позволит злоумышленнику с помощью JavaScript считать этот токен
Проблемы с перенаправлениями (redirects). Если redirect_uri неправильно настроен и не проверяется должным образом, злоумышленники могут подставить собственный URI и перехватить access_token. Это может привести к утечке токена, если сервер авторизации перенаправляет пользователя на контролируемый злоумышленником сайт
OpenID Connect
OAuth 2.0 — это протокол, предназначенный для авторизации, который позволяет одному приложению предоставлять доступ к своим данным и функциям другому приложению, без необходимости делиться учётными данными пользователя.
Однако OAuth 2.0 сам по себе не решает задачу аутентификации — подтверждения личности пользователя. Чтобы добавить эту возможность, был разработан OpenID Connect (OIDC) — слой, который дополняет OAuth 2.0, делая его полноценным решением для аутентификации.
OpenID Connect — это расширение протокола OAuth 2.0, которое добавляет возможность проверять личность пользователя, входящего в систему. Оно добавляет функциональность аутентификации, что позволяет приложениям не только получать доступ к данным пользователя, но и узнавать, кто именно вошёл в систему.
Таким образом, OIDC предоставляет возможность реализовать единую аутентификацию (SSO) — сценарии, при которых один и тот же логин может использоваться для доступа к нескольким приложениям.
Поток (flow) OpenID Connect
Поток авторизации в OpenID Connect очень похож на стандартный поток OAuth 2.0, но с некоторыми важными дополнениями:
Использование специального scope
. В запросе на авторизацию клиент указывает scope=openid
, что сообщает серверу авторизации, что требуется не только доступ к данным, но и информация о пользователе. Дополнительно могут указываться другие scope
, например, profile
или email
, чтобы запросить более детальную информацию о пользователе.
Вместе с access_token клиент получает ещё и id_token. id_token представляет собой JSON Web Token (JWT), который содержит информацию о пользователе: его уникальный идентификатор, имя, время аутентификации и срок действия токена. В отличие от access_token, который используется для доступа к API, id_token предназначен для подтверждения личности пользователя. Он позволяет клиенту убедиться, что пользователь успешно прошёл аутентификацию.
Клиент может проверять подлинность и целостность id_token самостоятельно, не обращаясь к серверу. Это достигается благодаря тому, что JWT подписывается на стороне сервера авторизации. Клиент может расшифровать JWT с помощью публичного ключа сервера и убедиться, что токен не был изменён, а также проверить его срок действия и другие параметры.
Если access_token был запрошен вместе с openid, клиент также может использовать его для получения дополнительных сведений о пользователе с помощью UserInfo Endpoint. Этот специальный эндпоинт на сервере авторизации позволяет запрашивать данные, такие как адрес электронной почты, имя пользователя, аватар и другую профильную информацию.
Заключение
OAuth 2.0 — это гибкий протокол авторизации, который можно использовать практически на любой платформе благодаря его совместимости с HTTP. Он поддерживается большинством крупных сервисов, что делает его хорошим выбором для интеграции с популярными API. С помощью различных схем авторизации, таких как Authorization Code Grant или Client Credentials, OAuth 2.0 позволяет настраивать уровень безопасности в зависимости от нужд приложения.
Особое внимание стоит уделять правильной настройке и защите токенов, особенно при использовании методов с redirect_uri. Это помогает избежать утечек и уязвимостей. Использование OAuth 2.0 с OpenID Connect добавляет возможность единого входа (SSO), что значительно улучшает пользовательский опыт.
Если вы выбираете OAuth 2.0 для своего проекта, это надёжное и проверенное решение при условии грамотной реализации.