Skip to content

Latest commit

 

History

History
762 lines (558 loc) · 58.8 KB

File metadata and controls

762 lines (558 loc) · 58.8 KB

en

Сервер авторизации

Модуль для 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:

  1. Заголовок Authorization: Bearer <JWT> — токен верифицируется локально через jwt-cpp
  2. Заголовки Session + Secret — межсервисная аутентификация (ID сессии передаётся напрямую)
  3. Cookie — читается заголовок X-Auth-Context: если "service" → cookie __Secure-SAT, иначе → cookie __Secure-AT. Токен из cookie верифицируется локально через jwt-cpp.

Пошаговый процесс обработки запроса POST /oauth2/token:

  1. Цикл событий рабочего процесса (epoll) принимает соединение и читает HTTP-запрос.
  2. AuthServer разбирает тип grant и учётные данные клиента из тела запроса или заголовка Authorization: Basic. Для гранта password без явного client_id автоматически используется клиент веб-приложения по умолчанию.
  3. Для приложений web и service: C++ проверяет redirect_uri и javascript_origins в процессе — по конфигурации провайдера, загруженной из conf/oauth2/, без обращения к БД.
  4. Выполняет асинхронный вызов daemon.token(client_id, client_secret, payload, agent, host). База данных берёт на себя всю логику grant-типов: генерацию токенов, создание сессий, refresh-токены, обмен токенами и валидацию внешних JWT.
  5. В случае успеха: C++ устанавливает три cookie (__Secure-AT, __Secure-RT с TTL 60 дней и SameSite=None; Secure; сессионный cookie SID) и возвращает клиенту 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 предоставляет владелец сервера.


1. Сервисный токен (без пользователя)

Используйте 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..."
}

2. Вход пользователя (логин + пароль)

Аутентификация конкретного пользователя. Время жизни токена: 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)

Полная документация: Аутентификация и авторизация

Аутентификация на основе cookies

Для разработчиков браузерных 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

Обе пары живут в браузере одновременно и не конфликтуют.

Что должен сделать фронтенд

  1. Добавить credentials: 'include' ко всем запросам.
  2. Передавать заголовок 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

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 и безопасность

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"
    ]
  }
}

Документация

Аутентификация и авторизация

Использование протокола OAuth 2.0 для авторизации пользователя

Протокол определяет четыре роли:

  • Владелец ресурса (resource owner) — пользователь системы (физическое лицо);
  • Клиент (client) — приложение, которое запрашивает доступ к защищаемому ресурсу от имени его владельца;
  • Сервер авторизации (authorization server) — сервер, который выпускает для клиента маркеры идентификации с разрешениями от владельца ресурса, а также маркеры доступа, позволяющие получать доступ к данным;
  • Поставщик ресурса (resource server) — сервер, обеспечивающий доступ к защищаемому ресурсу на основе проверки маркеров идентификации и маркеров доступа (например, к идентификационным данным пользователя).

В рамках данной реализации authorization server и resource server — это один и тот же сервер.

Для взаимодействия Клиента с Сервером необходимо получить идентификатор (client_id) и секрет (client_secret).

Взаимодействие происходит через RESTful API, описанное в спецификации RFC 6749.

Использование OpenID Connect для аутентификации пользователя

В общем виде схема аутентификации с использованием OpenID Connect выглядит следующим образом:

  • Клиент (client) готовит запрос на аутентификацию пользователя с необходимыми параметрами;
  • Клиент (client) отправляет GET запрос на аутентификацию в адрес сервера авторизации;
  • Сервер авторизации (authorization server) аутентифицирует пользователя (пользователь вводит логин и пароль);
  • Сервер авторизации (authorization server) получает согласие пользователя на проведение аутентификации в данной системе;
  • Сервер авторизации (authorization server) перенаправляет пользователя обратно Клиенту и передаёт код авторизации;
  • Клиент (client) отправляет POST запрос с использованием кода авторизации на получение маркера идентификации;
  • Клиент (client) получает ответ, содержащий необходимый маркер идентификации (меняет код авторизации на маркер доступа);
  • Клиент (client) проводит проверку маркера идентификации и извлекает из маркера идентификатор пользователя.

Далее детально будут рассмотрены формируемые Клиентом запросы и ответы от Сервера авторизации.

Конечные точки сервера авторизации (API)

Для авторизации:

GET /oauth2/authorize

Для получения маркера доступа:

POST /oauth2/token

Типы разрешения на авторизацию

Протокол OAuth 2 определяет четыре стандартных типа разрешения на авторизацию, каждый из которых полезен в определённых ситуациях, а также два расширенных типа, поддерживаемых данной реализацией:

  1. Код авторизации (Authorization Code) — используется с серверными приложениями (server-side applications).
  2. Неявный (Implicit) — используется мобильными или веб-приложениями (JavaScript), работающими на устройстве пользователя.
  3. Учётные данные владельца ресурса (Resource Owner Password Credentials) — используются доверенными приложениями, которые являются частью самого сервиса.
  4. Учётные данные клиента (Client Credentials) — используются при доступе клиента (приложения) к API без авторизации пользователя.
  5. Обмен маркерами (Token Exchange, RFC 8693) — используется для получения нового маркера доступа до истечения срока действия текущего.
  6. 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:8080
http://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:8080
http://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)

Тип разрешения на основе обмена маркерами (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 в качестве гранта авторизации

Данный тип гранта позволяет авторизоваться в системе по данным из 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_dEUmYiJYXubnTyweIMg

Фронтенд-приложение

AuthServer включает готовое Vue 3 + Vite одностраничное приложение для потоков аутентификации OAuth2. Оно находится в директории frontend/ и предоставляет:

  • Вход (/login) — логин/пароль и вход через Google
  • Регистрация (/register) — подтверждение email 6-значным кодом
  • Восстановление пароля (/recover) — сброс через email
  • Согласие OAuth2 (/authorize) — экран согласия для потока authorization code
  • Страница ошибки (/error) — понятное отображение ошибок

Ключевые характеристики

  • Никаких токенов в JavaScript — аутентификация только через cookies (HttpOnly cookies через 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

  1. Apostol CRM — шаблон-проект построенный на фреймворках A-POST-OL (C++20) и PostgreSQL Framework for Backend Development.