Модуль для Apostol CRM1.
Сервер авторизации — C++-модуль сервера OAuth 2.0 для фреймворка Апостол (C++20). Работает внутри рабочих процессов Апостола и обрабатывает все запросы, путь которых начинается с /oauth2/.
Ключевые характеристики:
- Написан на C++20, использует асинхронную неблокирующую модель ввода/вывода на основе API epoll.
- Реализует RFC 6749 (OAuth 2.0), OpenID Connect и RFC 7519 (JWT). Поддерживаемые типы разрешений: Authorization Code, Implicit, Resource Owner Password Credentials, Client Credentials, Token Exchange (RFC 8693) и JWT Bearer.
- Вся логика выдачи токенов и управления сессиями выполняется в базе данных (
daemon.token,daemon.login). Слой C++ занимается только HTTP-транспортом, валидацией параметров, криптографическими операциями с JWT и управлением cookies. - Верификация JWT выполняется локально с помощью библиотеки
jwt-cpp— поддерживаются все 12 алгоритмов (HS256/384/512, RS256/384/512, ES256/384/512, PS256/384/512) без обращения к базе данных. - Поддерживает внешних провайдеров идентификации (Google, ЕСИА) через стандартные JWKS-эндпоинты: открытые ключи загружаются и кэшируются в памяти.
- Упрощает вход пользователей, позволяя авторизоваться через существующую учётную запись (например, Google) без отдельной регистрации.
AuthServer регистрируется как обработчик всех запросов, путь которых начинается с /oauth2/ (см. CheckLocation). Принимает методы GET, POST и OPTIONS; все остальные HTTP-методы возвращают 405 Method Not Allowed.
Маршрутизация GET /oauth2/{action}:
| Действие | Что делает C++ | Вызов в БД |
|---|---|---|
authorize / auth |
Валидирует client_id, redirect_uri, response_type, scope, access_type, prompt по конфигурации провайдера в памяти. Перенаправляет на страницу входа из conf/sites/*.json (oauth2.identifier или oauth2.secret). |
Нет |
code |
Получает код авторизации из редиректа внешнего провайдера. Для внешних провайдеров (например, Google): выполняет прямой HTTP-вызов к token_uri провайдера для обмена кода на токен, затем верифицирует полученный JWT. |
daemon.login(token, agent, host, origin) |
callback |
Перенаправляет на oauth2.callback из conf/sites/*.json. |
Нет |
identifier |
Извлекает value из тела запроса. Аутентификация: (1) заголовок Authorization: Bearer, (2) заголовки Session+Secret, (3) cookie __Secure-AT/__Secure-SAT по заголовку X-Auth-Context. |
daemon.identifier(token, value) |
Маршрутизация POST /oauth2/{action}:
| Действие | Что делает C++ | Вызов в БД |
|---|---|---|
token |
Разбирает client_id/client_secret из тела запроса или заголовка Authorization: Basic. Для приложений web/service: проверяет redirect_uri и javascript_origins по конфигурации провайдера. Передаёт полный payload в БД как JSONB. |
daemon.token(client_id, client_secret, payload::jsonb, agent, host) |
identifier |
Аналогично GET identifier. | daemon.identifier(token, value) |
Приоритет аутентификации для endpoint identifier:
- Заголовок
Authorization: Bearer <JWT>— токен верифицируется локально через jwt-cpp - Заголовки
Session+Secret— межсервисная аутентификация (ID сессии передаётся напрямую) - Cookie — читается заголовок
X-Auth-Context: если"service"→ cookie__Secure-SAT, иначе → cookie__Secure-AT. Токен из cookie верифицируется локально через jwt-cpp.
Пошаговый процесс обработки запроса POST /oauth2/token:
- Цикл событий рабочего процесса (epoll) принимает соединение и читает HTTP-запрос.
AuthServerразбирает тип grant и учётные данные клиента из тела запроса или заголовкаAuthorization: Basic. Для грантаpasswordбез явногоclient_idавтоматически используется клиент веб-приложения по умолчанию.- Для приложений
webиservice: C++ проверяетredirect_uriиjavascript_originsв процессе — по конфигурации провайдера, загруженной изconf/oauth2/, без обращения к БД. - Выполняет асинхронный вызов
daemon.token(client_id, client_secret, payload, agent, host). База данных берёт на себя всю логику grant-типов: генерацию токенов, создание сессий, refresh-токены, обмен токенами и валидацию внешних JWT. - В случае успеха: C++ устанавливает три cookie (
__Secure-AT,__Secure-RTс TTL 60 дней иSameSite=None; Secure; сессионный cookieSID) и возвращает клиенту JSON-ответ с токеном.
Работа с JWT в C++:
Модуль использует библиотеку jwt-cpp для всех криптографических операций:
- Верификация — поддерживаются все 12 алгоритмов. Для HMAC-алгоритмов (HS256/384/512) секрет берётся из конфигурации провайдера. Для асимметричных алгоритмов (RS*/ES*/PS*) открытый ключ ищется по
kidв кэше JWKS в памяти. После асимметричной верификации токен переподписывается алгоритмом HS256 для единообразной внутренней обработки. - Создание — выпускает короткоживущий HS256-токен (TTL 1 час), подписанный секретом приложения
web, с издателем и аудиторией из конфигурации провайдера по умолчанию.
Heartbeat (запускается каждые 30 минут):
Метод Heartbeat() сбрасывает статусы ключей всех провайдеров в ksUnknown, затем загружает актуальные публичные ключи JWKS/сертификатов с cert_uri каждого провайдера через неблокирующий C++ HTTP-клиент. При ошибке загрузки следующая попытка назначается через 5 секунд вместо 30 минут. Кэшированные ключи используются в VerifyToken() для верификации подписей RS*/ES*/PS* без обращения к базе данных.
AuthServer тесно связан с модулями oauth2 и admin платформы db-platform.
Всё состояние аутентификации хранится в базе данных — C++-модуль занимается только HTTP-транспортом и локальной проверкой подписи JWT:
| Объект | Модуль | Назначение |
|---|---|---|
| OAuth2-клиенты | oauth2 |
Зарегистрированные приложения с client_id / client_secret |
| OAuth2-провайдеры | oauth2 |
Внешние провайдеры идентификации (Google, ЕСИА и др.) с URI JWKS/сертификатов |
| OAuth2-аудитории | oauth2 |
Аудитории токенов, привязанные к клиентам |
| Коды авторизации | oauth2 |
Кратковременные коды для потока Authorization Code |
| Сессии | admin |
Сессии пользователей, создаваемые после успешной аутентификации |
| Пользователи / пароли | admin |
Учётные записи пользователей, проверка учётных данных |
Вся логика grant-типов (выдача, обновление, обмен и проверка токенов) реализована в виде PL/pgSQL-функций, вызываемых модулем. Открытые ключи провайдеров загружаются с внешних URL и кэшируются локально в C++-модуле (обновляются каждые 30 минут).
Для фронтенд-разработчиков — чтобы получить токен, нужно всего два возможных запроса.
Все токены выдаются через один эндпоинт. Заголовок Authorization: Basic содержит пару client_id:client_secret, закодированную в Base64. Значения client_id, client_secret и scope предоставляет владелец сервера.
Используйте client_credentials для бэкенд-интеграций и межсервисных вызовов. Время жизни токена: 24 часа.
Запрос:
POST /oauth2/token HTTP/1.1
Host: YOUR-HOST
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>
grant_type=client_credentials&scope=YOUR-SCOPE&access_type=offlineОтвет 200 OK:
{
"access_token": "eyJ...",
"refresh_token": "mZfA...",
"secret": "n9RX...",
"token_type": "Bearer",
"expires_in": 86400,
"session": "db17c81d..."
}Аутентификация конкретного пользователя. Время жизни токена: 1 час.
Запрос:
POST /oauth2/token HTTP/1.1
Host: YOUR-HOST
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>
grant_type=password&username=USER&password=PASS&scope=YOUR-SCOPE&access_type=offlineОтвет 200 OK:
{
"access_token": "eyJ...",
"refresh_token": "85Hr...",
"secret": "0NTZ...",
"token_type": "Bearer",
"expires_in": 3600,
"session": "ca8db8e7..."
}Используйте access_token как Bearer-токен во всех последующих запросах к API:
Authorization: Bearer eyJ...
Сервер автоматически устанавливает HttpOnly-куки:
| Тип гранта | Устанавливаемые cookies |
|---|---|
password, authorization_code, refresh_token, jwt-bearer |
__Secure-AT, __Secure-RT, SID (сессия пользователя) |
client_credentials |
__Secure-SAT, __Secure-SRT (сервисная сессия, без SID) |
Полная документация: Аутентификация и авторизация
Для разработчиков браузерных SPA — никакого кода для управления токенами.
Сервер реализует паттерн Token Handler из коробки. Две независимые пары cookies сосуществуют для разных контекстов аутентификации:
| Контекст | Cookies | Устанавливаются при | Назначение |
|---|---|---|---|
| Пользователь | __Secure-AT, __Secure-RT, SID |
password / authorization_code |
Операции авторизованного пользователя |
| Сервис | __Secure-SAT, __Secure-SRT |
client_credentials |
Публичные/гостевые операции (регистрация, публичные данные) |
Все cookies — HttpOnly; Secure; SameSite=None. Браузер отправляет их автоматически — фронтенд никогда не читает и не управляет токенами напрямую.
Контекст пользователя (по умолчанию):
Браузерное SPA
└─ fetch('/api/v1/...', { credentials: 'include' })
↓
Apostol AppServer
1. Читает __Secure-AT из cookie
2. Проверяет токен — если истёк:
a. Вызывает daemon.refresh_token() → новые токены
b. Устанавливает новые cookies __Secure-AT, __Secure-RT, SID
c. Прозрачно повторяет оригинальный запрос
3. Возвращает API-ответ + заголовки Set-Cookie
Сервисный контекст — фронтенд передаёт заголовок X-Auth-Context: service:
Браузерное SPA
└─ fetch('/api/v1/...', { credentials: 'include', headers: { 'X-Auth-Context': 'service' } })
↓
Apostol AppServer
1. Читает заголовок X-Auth-Context → выбирает cookie __Secure-SAT
2. Проверяет токен — если истёк:
a. Вызывает daemon.refresh_token() → новые токены
b. Устанавливает новые cookies __Secure-SAT, __Secure-SRT
c. Прозрачно повторяет оригинальный запрос
3. Возвращает API-ответ + заголовки Set-Cookie
Обе пары живут в браузере одновременно и не конфликтуют.
- Добавить
credentials: 'include'ко всем запросам. - Передавать заголовок
X-Auth-Context: serviceдля сервисных (гостевых/публичных) запросов.
// 1. Сервисная аутентификация (один раз, до входа пользователя — например, на странице регистрации)
await fetch('/oauth2/token', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'grant_type=client_credentials&client_id=CLIENT&scope=SCOPE'
});
// Cookies __Secure-SAT, __Secure-SRT устанавливаются автоматически.
// 2. Сервисные API-вызовы (публичные данные, регистрация и т.д.)
const data = await fetch('/api/v1/sign/up', {
method: 'POST',
credentials: 'include',
headers: { 'X-Auth-Context': 'service' },
body: JSON.stringify({ username: 'new_user', ... })
});
// 3. Вход пользователя
await fetch('/oauth2/token', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'grant_type=password&username=USER&password=PASS&...'
});
// Cookies __Secure-AT, __Secure-RT, SID устанавливаются автоматически.
// 4. Пользовательские API-вызовы (контекст по умолчанию, дополнительный заголовок не нужен)
const profile = await fetch('/api/v1/whoami', { credentials: 'include' });
// Обновление токена прозрачно — всегда получаете 200.401 Unauthorized означает, что сессия полностью истекла (оба токена — access и refresh — недействительны). Перенаправьте на страницу входа:
if (response.status === 401) {
window.location.href = '/login';
}| Не делайте | Почему |
|---|---|
Хранить токены в localStorage / sessionStorage |
Уязвимо для XSS; cookies — HttpOnly, JS не может их прочитать |
Отслеживать expires_in и планировать обновление |
AppServer обновляет токен прозрачно при каждом просроченном запросе |
| Писать логику refresh | Уже реализована на стороне сервера |
Отправлять заголовок Authorization: Bearer |
При использовании cookies не нужен (оба режима поддерживаются одновременно) |
CORS проверяется по allowlist-у origins, загруженному из conf/oauth2/*.json (поля javascript_origins). Запросы с неизвестных origins не получают Access-Control-Allow-Credentials: true, поэтому браузер блокирует отправку cookies из недоверенных сайтов.
Чтобы разрешить origin вашего фронтенда, добавьте его в javascript_origins в конфиге провайдера:
{
"web": {
"javascript_origins": [
"https://your-app.com",
"http://localhost:3000"
]
}
}Протокол определяет четыре роли:
- Владелец ресурса (
resource owner) — пользователь системы (физическое лицо); - Клиент (
client) — приложение, которое запрашивает доступ к защищаемому ресурсу от имени его владельца; - Сервер авторизации (
authorization server) — сервер, который выпускает для клиента маркеры идентификации с разрешениями от владельца ресурса, а также маркеры доступа, позволяющие получать доступ к данным; - Поставщик ресурса (
resource server) — сервер, обеспечивающий доступ к защищаемому ресурсу на основе проверки маркеров идентификации и маркеров доступа (например, к идентификационным данным пользователя).
В рамках данной реализации authorization server и resource server — это один и тот же сервер.
Для взаимодействия Клиента с Сервером необходимо получить идентификатор (client_id) и секрет (client_secret).
Взаимодействие происходит через RESTful API, описанное в спецификации RFC 6749.
В общем виде схема аутентификации с использованием OpenID Connect выглядит следующим образом:
- Клиент (
client) готовит запрос на аутентификацию пользователя с необходимыми параметрами; - Клиент (
client) отправляетGETзапрос на аутентификацию в адрес сервера авторизации; - Сервер авторизации (
authorization server) аутентифицирует пользователя (пользователь вводит логин и пароль); - Сервер авторизации (
authorization server) получает согласие пользователя на проведение аутентификации в данной системе; - Сервер авторизации (
authorization server) перенаправляет пользователя обратно Клиенту и передаёт код авторизации; - Клиент (
client) отправляетPOSTзапрос с использованием кода авторизации на получение маркера идентификации; - Клиент (
client) получает ответ, содержащий необходимый маркер идентификации (меняет код авторизации на маркер доступа); - Клиент (
client) проводит проверку маркера идентификации и извлекает из маркера идентификатор пользователя.
Далее детально будут рассмотрены формируемые Клиентом запросы и ответы от Сервера авторизации.
Для авторизации:
GET /oauth2/authorizeДля получения маркера доступа:
POST /oauth2/tokenПротокол OAuth 2 определяет четыре стандартных типа разрешения на авторизацию, каждый из которых полезен в определённых ситуациях, а также два расширенных типа, поддерживаемых данной реализацией:
- Код авторизации (
Authorization Code) — используется с серверными приложениями (server-side applications). - Неявный (
Implicit) — используется мобильными или веб-приложениями (JavaScript), работающими на устройстве пользователя. - Учётные данные владельца ресурса (
Resource Owner Password Credentials) — используются доверенными приложениями, которые являются частью самого сервиса. - Учётные данные клиента (
Client Credentials) — используются при доступе клиента (приложения) к API без авторизации пользователя. - Обмен маркерами (
Token Exchange, RFC 8693) — используется для получения нового маркера доступа до истечения срока действия текущего. - JWT Bearer — используется для авторизации с помощью JWT маркера, выпущенного внешней системой (например, Google).
Код авторизации является одним из наиболее распространённых типов разрешения на авторизацию, поскольку он хорошо подходит для серверных приложений, где исходный код приложения и секрет клиента не доступны посторонним. Процесс строится на перенаправлении (redirection), что означает, что приложение должно быть в состоянии взаимодействовать с пользовательским агентом (user-agent), например, веб-браузером, и получать коды авторизации API, перенаправляемые через пользовательский агент.
Параметры запроса:
| Поле | Значение | Описание |
|---|---|---|
| client_id | client_id |
Обязательный. Идентификатор клиента (приложения). |
| redirect_uri | redirect_uri |
Обязательный. URI, на который сервер авторизации перенаправит агента пользователя (браузер) и код авторизации. |
| response_type | code | Обязательный. Указывает на то, что приложение запрашивает доступ с помощью кода авторизации. |
| scope | scope |
Рекомендуемый. Список областей, разделённых пробелами, которые определяют ресурсы, к которым ваше приложение может получить доступ от имени пользователя. |
| access_type | access_type |
Рекомендуемый. Указывает, может ли ваше приложение обновлять маркеры доступа, когда пользователь отсутствует в браузере. Допустимые значения: online (по умолчанию) и offline. |
| state | state |
Рекомендуемый. Набор случайных символов, которые будут возвращены сервером клиенту (используется для защиты от повторных запросов). |
| prompt | prompt |
Необязательный. Управляет типом отображаемой страницы входа. Допустимые значения: signin (по умолчанию), secret (страница ввода пароля), consent, select_account, none. При значении secret пользователь перенаправляется на страницу ввода пароля (oauth2.secret в conf/sites/*.json), а не на страницу идентификации. |
Пример запроса:
GET /oauth2/authorize?client_id=YOUR-CLIENT-ID&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcode&scope=api&response_type=code&access_type=online&state=c2FmZXR HTTP/1.1
Host: localhost:8080http://localhost:8080/oauth2/authorize?
client_id=YOUR-CLIENT-ID&
redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcode&
response_type=code&
access_type=online&
scope=api&
state=c2FmZXR
Если в ходе аутентификации не возникло ошибок, сервер авторизации перенаправит пользователя по ссылке, указанной в redirect_uri, и вернёт два обязательных параметра:
code— код авторизации;state— значение параметраstate, которое было получено в запросе на аутентификацию.
Клиент должен провести сравнение отправленного и полученного параметра state.
Ответ с кодом авторизации:
http://localhost:8080/oauth2/code?code=b%2F8NpjbB4eLaukGr68tE7maTCeBISO%2FC7hWxKGuKb8I4Ysc7uw8a2MRUMWnO3Nzt
- Обратите внимание, что код (
b/8NpjbB4eLaukGr68tE7maTCeBISO/C7hWxKGuKb8I4Ysc7uw8a2MRUMWnO3Nzt) закодирован алгоритмом URL encode и его необходимо декодировать алгоритмом URL Decode перед использованием.
Если в ходе аутентификации возникла ошибка, сервер авторизации перенаправит пользователя по ссылке, указанной в redirect_uri, с информацией об ошибке:
http://localhost:8080/oauth2/code?code=403&error=access_denied&error_description=Access%20denied.
Для обмена кода авторизации на маркер доступа Клиент должен сформировать запрос методом POST.
Параметры запроса:
| Поле | Значение | Описание |
|---|---|---|
| client_id | client_id |
Обязательный. Идентификатор клиента. |
| client_secret | client_secret |
Обязательный. Секрет клиента. |
| grant_type | authorization_code | Обязательный. Как определено в спецификации OAuth 2.0, это поле должно содержать значение authorization_code. |
| code | code |
Обязательный. Код авторизации, возвращённый из первоначального запроса. |
| redirect_uri | redirect_uri |
Обязательный. URI переадресации (должен совпадать с redirect_uri из первоначального запроса). |
- Согласно спецификации OAuth 2.0, параметры авторизации клиента (
client_idиclient_secret) могут быть переданы как в теле запроса, так и в HTTP заголовкеAuthorization(HTTP Basic authentication):
Authorization: Basic d2ViLXNlcnZpY2UucnU6Y2xpZW50IHNlY3JldA==
В ответ на запрос сервер авторизации вернёт объект JSON, который содержит маркер краткосрочного доступа и маркер обновления.
Ответ содержит следующие поля:
| Поле | Тип | Описание |
|---|---|---|
| access_token | STRING | Маркер краткосрочного доступа (сроком действия 1 час). |
| expires_in | INTEGER | Оставшееся время жизни маркера доступа в секундах. |
| token_type | STRING | Тип возвращаемого маркера. Значение всегда Bearer. |
| session | STRING | Идентификатор сессии пользователя. |
| refresh_token | STRING | * Маркер, который вы можете использовать для получения нового маркера доступа. |
| id_token | STRING | * Маркер пользователя. |
- Обратите внимание, что маркер обновления возвращается только в том случае, если ваше приложение в первоначальном запросе установило в
access_typeзначениеoffline. - Обратите внимание, что маркер пользователя (
id_token) возвращается только в том случае, если ваше приложение установило вscopeодно из значений:openid,profileилиemail.
Пример запроса:
POST http://localhost:8080/oauth2/token
Content-Type: application/x-www-form-urlencoded
client_id=YOUR-CLIENT-ID&
client_secret=YOUR-CLIENT-SECRET&
grant_type=authorization_code&
code=b%2F8NpjbB4eLaukGr68tE7maTCeBISO%2FC7hWxKGuKb8I4Ysc7uw8a2MRUMWnO3Nzt&
redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcode* Хоть это и не определено спецификацией, но сервер авторизации примет запрос и в формате JSON (Content-Type: application/json).
Пример ответа:
{
"access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.[payload].[signature]",
"token_type" : "Bearer",
"expires_in" : 3600,
"session" : "dfe05b78a76b6ad8e0fcbef270671793b86aa848"
}Неявный тип разрешения на авторизацию используется мобильными и веб-приложениями (приложениями, которые работают в веб-браузере — JavaScript), где конфиденциальность секрета клиента не может быть гарантирована. Неявный тип разрешения также основан на перенаправлении пользовательского агента, при этом маркер доступа передаётся пользовательскому агенту для дальнейшей передачи приложению. Это делает маркер доступным пользователю и другим приложениям на устройстве пользователя. При данном типе разрешения не осуществляется аутентификация подлинности приложения, а процесс полагается на URI перенаправления, зарегистрированный в сервере авторизации.
- Маркеры пользователя (
id_token) не включаются во фрагмент редиректа. Маркеры обновления (refresh_token) включаются только если они возвращены базой данных.
Параметры запроса:
| Поле | Значение | Описание |
|---|---|---|
| client_id | client_id |
Обязательный. Идентификатор клиента (приложения). |
| redirect_uri | redirect_uri |
Обязательный. URI, на который сервер авторизации перенаправит агента пользователя (браузер) и маркер доступа. |
| response_type | token | Обязательный. JavaScript-приложения должны установить значение параметра в token. Это значение указывает серверу авторизации возвращать маркер доступа в виде пары name=value в идентификаторе фрагмента URI (#), на который перенаправляется пользователь после завершения процесса авторизации. |
| scope | scope |
Рекомендуемый. Список областей, разделённых пробелами, которые определяют ресурсы, к которым ваше приложение может получить доступ от имени пользователя. |
| state | state |
Рекомендуемый. Набор случайных символов, которые будут возвращены сервером клиенту (используется для защиты от повторных запросов). |
Пример запроса:
GET /oauth2/authorize?client_id=YOUR-CLIENT-ID&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&scope=api&response_type=token&state=c2FmZXR HTTP/1.1
Host: localhost:8080http://localhost:8080/oauth2/authorize?
client_id=YOUR-CLIENT-ID&
redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&
response_type=token&
scope=api&
state=c2FmZXR
Маркер доступа или сообщение об ошибке возвращаются во фрагменте хэша URI перенаправления:
Ответ с маркером доступа:
http://localhost:8080/callback#token_type=Bearer&expires_in=3600&session=dfe05b78a76b6ad8e0fcbef270671793b86aa848&access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.[payload].[signature]
-
В дополнение к параметру
access_tokenстрока фрагмента также содержит параметрtoken_type(всегдаBearer) и параметрexpires_in, который указывает время жизни маркера в секундах. Если параметрstateбыл указан в запросе маркера доступа, его значение также включается в ответ. -
URI перенаправления — в данном примере
http://localhost:8080/callback/index.html— должен указывать на веб-страницу, которая содержит скрипт для извлечения маркера доступа из URI перенаправления.
Ответ с ошибкой:
http://localhost:8080/callback#code=403&error=access_denied&error_description=Access%20denied.
Сервер авторизации поддерживает гибридный режим типов разрешения. Если в параметре response_type указать через пробел оба значения code token, сервер авторизации вернёт в одном ответе и код авторизации, и маркер доступа.
При этом типе разрешения пользователь предоставляет приложению напрямую свои учётные данные (имя пользователя и пароль). Приложение, в свою очередь, использует полученные учётные данные для получения маркера доступа от сервера авторизации. Этот тип разрешения должен использоваться только в том случае, когда другие варианты недоступны, и только когда приложение пользуется доверием пользователя (например, является частью самой системы).
После того как пользователь передаст свои учётные данные приложению, приложение запросит маркер доступа у сервера авторизации методом POST.
Параметры запроса:
| Поле | Значение | Описание |
|---|---|---|
| client_id | client_id |
Обязательный. Идентификатор клиента. |
| client_secret | client_secret |
Обязательный. Секрет клиента. |
| grant_type | password | Обязательный. Как определено в спецификации OAuth 2.0, это поле должно содержать значение password. |
| username | username |
Вариативный. Логин пользователя. Игнорируется, если указано значение в поле secret. |
| password | password |
Вариативный. Пароль пользователя. Игнорируется, если указано значение в поле secret. |
| secret | secret |
Вариативный. Секретный код. Если значение указано, поля username и password заполнять не нужно. |
| scope | scope |
Рекомендуемый. Список областей, разделённых пробелами, которые определяют ресурсы, к которым ваше приложение может получить доступ от имени пользователя. |
- Согласно спецификации OAuth 2.0, параметры авторизации клиента (
client_idиclient_secret) могут быть переданы как в теле запроса, так и в HTTP заголовкеAuthorization(HTTP Basic authentication):
Authorization: Basic d2ViLXNlcnZpY2UucnU6Y2xpZW50IHNlY3JldA==
В ответ на запрос сервер авторизации вернёт объект JSON, который содержит маркер краткосрочного доступа и маркер обновления.
Ответ содержит следующие поля:
| Поле | Тип | Описание |
|---|---|---|
| access_token | STRING | Маркер краткосрочного доступа (сроком действия 1 час). |
| expires_in | INTEGER | Оставшееся время жизни маркера доступа в секундах. |
| token_type | STRING | Тип возвращаемого маркера. Значение всегда Bearer. |
| session | STRING | Идентификатор сессии пользователя. |
| refresh_token | STRING | Маркер, который вы можете использовать для получения нового маркера доступа. |
| id_token | STRING | * Маркер пользователя. |
- Обратите внимание, что маркер пользователя (
id_token) возвращается только в том случае, если ваше приложение установило вscopeодно из значений:openid,profileилиemail.
Пример запроса:
POST http://localhost:8080/oauth2/token
Content-Type: application/x-www-form-urlencoded
client_id=YOUR-CLIENT-ID&
client_secret=YOUR-CLIENT-SECRET&
grant_type=password&
username=admin&
password=admin* Хоть это и не определено спецификацией, но сервер авторизации примет запрос и в формате JSON (Content-Type: application/json).
Если учётные данные и клиента, и пользователя корректны, сервер авторизации вернёт маркер доступа для приложения.
Тип разрешения с использованием учётных данных клиента позволяет приложению осуществлять доступ к своему собственному аккаунту сервиса. Это может быть полезно, например, когда приложение хочет обновить собственную регистрационную информацию или URI перенаправления на сервисе, либо получить доступ к другой информации, хранимой в аккаунте приложения, через API.
Параметры запроса:
| Поле | Значение | Описание |
|---|---|---|
| client_id | client_id |
Обязательный. Идентификатор клиента. |
| client_secret | client_secret |
Обязательный. Секрет клиента. |
| grant_type | client_credentials | Обязательный. Как определено в спецификации OAuth 2.0, это поле должно содержать значение client_credentials. |
| scope | scope |
Рекомендуемый. Список областей, разделённых пробелами, которые определяют ресурсы, к которым ваше приложение может получить доступ от имени пользователя. |
- Согласно спецификации OAuth 2.0, параметры авторизации клиента (
client_idиclient_secret) могут быть переданы как в теле запроса, так и в HTTP заголовкеAuthorization(HTTP Basic authentication):
Authorization: Basic d2ViLXNlcnZpY2UucnU6Y2xpZW50IHNlY3JldA==
В ответ на запрос сервер авторизации вернёт объект JSON, который содержит маркер доступа и маркер обновления.
Ответ содержит следующие поля:
| Поле | Тип | Описание |
|---|---|---|
| access_token | STRING | Маркер доступа (сроком действия 1 день). |
| expires_in | INTEGER | Оставшееся время жизни маркера доступа в секундах. |
| token_type | STRING | Тип возвращаемого маркера. Значение всегда Bearer. |
| session | STRING | Идентификатор сессии пользователя. |
| refresh_token | STRING | Маркер, который вы можете использовать для получения нового маркера доступа. |
| id_token | STRING | * Маркер пользователя. |
- Обратите внимание, что маркер пользователя (
id_token) возвращается только в том случае, если ваше приложение установило вscopeодно из значений:openid,profileилиemail.
Пример запроса:
POST http://localhost:8080/oauth2/token
Content-Type: application/x-www-form-urlencoded
client_id=YOUR-CLIENT-ID&
client_secret=YOUR-CLIENT-SECRET&
grant_type=client_credentialsПосле истечения срока действия маркера доступа все запросы к API с его использованием будут возвращать ошибку с кодом 403 ("Token expired"). Если при создании маркера доступа был создан маркер обновления (refresh_token), последний может быть использован для получения нового маркера доступа от сервера авторизации.
Параметры запроса:
| Поле | Значение | Описание |
|---|---|---|
| client_id | client_id |
Обязательный. Идентификатор клиента. |
| client_secret | client_secret |
Обязательный. Секрет клиента. |
| grant_type | refresh_token | Обязательный. Как определено в спецификации OAuth 2.0, это поле должно содержать значение refresh_token. |
| refresh_token | refresh_token |
Обязательный. Маркер обновления, выданный ранее. |
| scope | scope |
Рекомендуемый. Список областей, разделённых пробелами, которые определяют ресурсы, к которым ваше приложение может получить доступ от имени пользователя. |
- Согласно спецификации OAuth 2.0, параметры авторизации клиента (
client_idиclient_secret) могут быть переданы как в теле запроса, так и в HTTP заголовкеAuthorization(HTTP Basic authentication):
Authorization: Basic d2ViLXNlcnZpY2UucnU6Y2xpZW50IHNlY3JldA==
Пример запроса:
POST http://localhost:8080/oauth2/token
Content-Type: application/x-www-form-urlencoded
client_id=YOUR-CLIENT-ID&
client_secret=YOUR-CLIENT-SECRET&
grant_type=refresh_token&
refresh_token=e%2FdtGmXCIzHvPMURn%2FTH9udTPxtKpR5FFifx2uvH1WqT4myXLtgyjkLgYDy7g3Ik5MrFRR82* Хоть это и не определено спецификацией, но сервер авторизации примет запрос и в формате JSON (Content-Type: application/json).
Если учётные данные клиента корректны, сервер авторизации вернёт новый маркер краткосрочного доступа и новый маркер обновления.
Тип разрешения на основе обмена маркерами (RFC 8693) позволяет получить новый маркер доступа до истечения срока действия текущего.
Параметры запроса:
| Поле | Значение | Описание |
|---|---|---|
| client_id | client_id |
Обязательный. Идентификатор клиента. |
| client_secret | client_secret |
Обязательный. Секрет клиента. |
| grant_type | urn:ietf:params:oauth:grant-type:token-exchange | Обязательный. Как определено в спецификации, это поле должно содержать значение urn:ietf:params:oauth:grant-type:token-exchange. |
| subject_token | subject_token |
Обязательный. Маркер, выданный ранее. |
| subject_token_type | subject_token_type |
Рекомендуемый. Тип передаваемого маркера. Доступные значения: urn:ietf:params:oauth:token-type:jwt (по умолчанию), urn:ietf:params:oauth:token-type:access_token, urn:ietf:params:oauth:token-type:refresh_token, urn:ietf:params:oauth:token-type:id_token. |
| scope | scope |
Рекомендуемый. Список областей, разделённых пробелами, которые определяют ресурсы, к которым ваше приложение может получить доступ от имени пользователя. |
- Согласно спецификации OAuth 2.0, параметры авторизации клиента (
client_idиclient_secret) могут быть переданы как в теле запроса, так и в HTTP заголовкеAuthorization(HTTP Basic authentication):
Authorization: Basic d2ViLXNlcnZpY2UucnU6Y2xpZW50IHNlY3JldA==
Пример запроса:
POST http://localhost:8080/oauth2/token
Content-Type: application/x-www-form-urlencoded
client_id=YOUR-CLIENT-ID&
client_secret=YOUR-CLIENT-SECRET&
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&
subject_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.[сокращено для краткости].NorYsi-Ht826HUFCEArVZ60_dEUmYiJYXubnTyweIMg&
subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Ajwt* Хоть это и не определено спецификацией, но сервер авторизации примет запрос и в формате JSON (Content-Type: application/json).
Если учётные данные клиента корректны и у переданного маркера доступа не истёк срок, сервер авторизации вернёт новый маркер краткосрочного доступа и новый маркер обновления.
Данный тип гранта позволяет авторизоваться в системе по данным из JWT маркера, выпущенного другой (внешней) системой, например Google.
Параметры запроса:
| Поле | Значение | Описание |
|---|---|---|
| client_id | client_id |
Рекомендуемый. Идентификатор клиента. Не проверяется на транспортном уровне для данного типа гранта — весь payload передаётся в базу данных без валидации. |
| client_secret | client_secret |
Рекомендуемый. Секрет клиента. Не проверяется на транспортном уровне для данного типа гранта — весь payload передаётся в базу данных без валидации. |
| grant_type | urn:ietf:params:oauth:grant-type:jwt-bearer | Обязательный. Это поле должно содержать значение urn:ietf:params:oauth:grant-type:jwt-bearer. |
| assertion | assertion |
Обязательный. JWT маркер, выданный другой (внешней) системой. |
- Согласно спецификации OAuth 2.0, параметры авторизации клиента (
client_idиclient_secret) могут быть переданы как в теле запроса, так и в HTTP заголовкеAuthorization(HTTP Basic authentication):
Authorization: Basic d2ViLXNlcnZpY2UucnU6Y2xpZW50IHNlY3JldA==
Пример запроса:
POST http://localhost:8080/oauth2/token
Content-Type: application/x-www-form-urlencoded
client_id=YOUR-CLIENT-ID&
client_secret=YOUR-CLIENT-SECRET&
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&
assertion=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.[сокращено для краткости].NorYsi-Ht826HUFCEArVZ60_dEUmYiJYXubnTyweIMgAuthServer включает готовое Vue 3 + Vite одностраничное приложение для потоков аутентификации OAuth2. Оно находится в директории frontend/ и предоставляет:
- Вход (
/login) — логин/пароль и вход через Google - Регистрация (
/register) — подтверждение email 6-значным кодом - Восстановление пароля (
/recover) — сброс через email - Согласие OAuth2 (
/authorize) — экран согласия для потока authorization code - Страница ошибки (
/error) — понятное отображение ошибок
- Никаких токенов в JavaScript — аутентификация только через cookies (
HttpOnlycookies черезcredentials: 'include'). НикакогоlocalStorage, никакого кода управления токенами. - Конфигурация на этапе сборки — все проектно-специфичные значения (хост API, client ID, брендинг) передаются через переменные окружения
VITE_*. Для разных проектов не нужно менять код. - Интернационализация — встроенная поддержка i18n (английский + русский). Дополнительные локали добавляются JSON-файлами в
src/i18n/. - Переиспользуемость между проектами — фронтенд спроектирован как общий компонент модуля AuthServer. Каждый проект на Апостоле собирает свой экземпляр со своим
.envи деплоит наauth.<домен>.
auth.example.com ← nginx отдаёт frontend dist/
├─ /login ← Vue SPA (все маршруты → index.html)
├─ /register
├─ /recover
├─ /authorize
├─ /oauth2/* ← nginx проксирует на бэкенд Апостол
└─ /api/* ← nginx проксирует на бэкенд Апостол
Фронтенд деплоится как same-origin прокси: nginx отдаёт статическое SPA и проксирует запросы /oauth2/ и /api/ на бэкенд Апостол. Это полностью устраняет проблемы с CORS.
Полная инструкция по интеграции:
frontend/README.md(ru).
{
"modules": {
"AuthServer": {
"enabled": true
}
}
}Следуйте указаниям по сборке и установке Апостол (C++20).
Footnotes
-
Apostol CRM — шаблон-проект построенный на фреймворках A-POST-OL (C++20) и PostgreSQL Framework for Backend Development. ↩