Тема
Аутентификация и авторизация
Материал проходит ревью.
Границы темы
Scope: путь обычного backend-запроса: credential подтверждает контроль identity, система создаёт principal/session и отдельно решает, разрешено ли действие над конкретным ресурсом в текущем контексте.
Не рассматриваем: внутреннюю криптографию passkeys, полные OAuth/OIDC flows и синтаксис конкретного framework. Модель должна сохраняться при смене протокола.
Что нужно знать заранее
Статья «Идентификация» объясняет, как создаётся account и чем identifier отличается от authenticator. Также требуется базовая модель HTTP-запроса.
Результаты обучения
После статьи читатель сможет:
- разделить identification, authentication, session и authorization;
- проследить происхождение principal от credential;
- принять authorization decision по action, resource и context;
- выбрать 401 или 403 по HTTP semantics;
- сравнить session и token models по revocation и blast radius;
- написать негативные object-level и tenant-level tests.
Основная модель
Проверка контроля одного или нескольких authenticators, связанных с accountсоздаёт уверенность в том, кто действует сейчас. Авторизация отвечает на другой вопрос: разрешено ли этому principal выполнить это действие над этим ресурсом в этом контексте.
Диаграмма
Наличие user ID в JWT, cookie или header не делает его доверенным. Доверие появляется только после проверки механизма, issuer/audience/expiry или server-side session и связи результата с активным account.
Основные термины
| Термин | Определение | Не путать с |
|---|---|---|
| Authenticator | То, чем subscriber доказывает контроль account | Identifier или display name |
| Credential | Структура, связывающая identity и authenticator | Любая строка из запроса |
| Claimant | Субъект, пытающийся пройти authentication | Уже доверенный principal |
| Principal | Проверенная identity в контексте запроса | Полный профиль пользователя |
| Session | Ограниченный во времени контекст после authentication | Вечная identity |
| Policy | Правило принятия allow/deny decision | UI visibility |
| Resource | Конкретный объект или функция, к которым запрошен доступ | Только URL path |
| Revocation | Досрочное прекращение доверия к session, token или permission | Обычный expiry |
Четыре разных вопроса
| Этап | Вопрос | Результат |
|---|---|---|
| Identification | Какую identity различает система? | Account и identifier |
| Authentication | Контролирует ли claimant bound authenticator? | Principal и assurance context |
| Session | Как сохранить результат ограниченное время? | Session ID или access token |
| Authorization | Можно ли выполнить действие сейчас? | Allow/deny + reason/evidence |
Смешение этапов создаёт ошибки: email в URL идентифицирует запись, но не аутентифицирует actor; role в token может быть подлинной, но устаревшей; скрытая кнопка выражает UX, но не авторизует API.
End-to-end сценарий: чтение заказа
Диаграмма
Критичные свойства:
- client не задаёт доверенный
user_id,roleилиtenant_id; - resource загружается до object-level decision;
- policy получает action, owner, tenant и context;
- handler выполняется только после allow;
- audit не содержит session secret, но позволяет восстановить decision.
Authentication под капотом
По NIST SP 800-63B-4 успешная authentication демонстрирует контроль authenticator, связанного с subscriber account. Backend обычно выполняет цепочку:
- извлечь credential из определённого transport;
- проверить целостность и происхождение;
- проверить expiry, audience, nonce или session state по модели;
- найти binding с активным account;
- учесть revocation и required assurance;
- создать минимальный principal для запроса;
- зафиксировать outcome без записи секретов.
Session и access token
Вопрос не сводится к «JWT или cookie»: cookie — transport/storage mechanism клиента, JWT — формат claims. Возможны разные комбинации.
| Модель | Источник текущего состояния | Отзыв | Основной риск |
|---|---|---|---|
| Server-side session + opaque ID | Session store | Прямой delete/disable | Доступность и масштаб store |
| Opaque access token + introspection | Authorization server | Централизованный | Network dependency и caching |
| Self-contained signed token | Claims внутри token | До expiry сложнее | Stale permissions и широкий lifetime |
Для любой модели определите:
- короткий срок действия и idle/absolute timeout;
- rotation при изменении privilege и чувствительных событий;
- revoke при компрометации, logout или disable account;
- audience и допустимые consumers;
- защиту transport/storage;
- поведение при недоступности verifier;
- запрет логирования raw secrets.
Authorization decision
Полезная сигнатура policy:
text
can(subject, action, resource, context) -> allow | deny + reasonПроверка только роли часто недостаточна. support может читать один tenant, но не другой; владелец заказа может читать его, но refund требует дополнительного permission и свежей authentication.
ts
type Principal = { userId: string; tenantId: string; permissions: string[] };
type Order = { id: string; tenantId: string; ownerId: string };
export function canReadOrder(principal: Principal, order: Order): boolean {
if (!principal.permissions.includes("orders:read")) return false;
if (principal.tenantId !== order.tenantId) return false;
return principal.userId === order.ownerId || principal.permissions.includes("orders:read:any");
}go
func CanReadOrder(principal Principal, order Order) bool {
if !principal.HasPermission("orders:read") {
return false
}
if principal.TenantID != order.TenantID {
return false
}
return principal.UserID == order.OwnerID || principal.HasPermission("orders:read:any")
}php
<?php
function canReadOrder(Principal $principal, Order $order): bool
{
if (!$principal->hasPermission('orders:read')) {
return false;
}
if ($principal->tenantId !== $order->tenantId) {
return false;
}
return $principal->userId === $order->ownerId
|| $principal->hasPermission('orders:read:any');
}Ожидаемый результат
| Principal | Resource | Результат |
|---|---|---|
Owner, тот же tenant, orders:read | Свой заказ | allow |
| Owner, другой tenant | Чужой tenant | deny |
Тот же tenant без orders:read | Любой заказ | deny |
Support с orders:read:any в tenant | Заказ tenant | allow |
Порядок guards выражает deny by default: отсутствие каждого обязательного условия ведёт к deny.
401, 403 и 404
RFC 9110 задаёт semantics:
| Ситуация | Обычно | Важная деталь |
|---|---|---|
| Credentials отсутствуют или не подходят | 401 Unauthorized | Ответ содержит применимый WWW-Authenticate challenge |
| Credentials действительны, но недостаточны | 403 Forbidden | Повтор той же authentication не решает запрет |
| Нужно скрыть существование запрещённого ресурса | 404 Not Found может использоваться | Внутренний audit сохраняет реальную причину |
Название 401 Unauthorized исторически сбивает с толку: по semantics это прежде всего authentication challenge. Не превращайте любой отказ policy в 401 — клиент может бессмысленно повторять login.
Минимальный воспроизводимый тест
Составьте authorization matrix для GET /orders/:id:
text
case expected
owner + same tenant 200
other user + same tenant 403 or concealed 404 by policy
owner id + other tenant 404/deny; never leak resource
valid session, missing perm 403
expired session 401 + challenge
no session 401 + challenge
disabled account 401 or session invalidation policyКак проверить
Тест должен создавать реальные resources с разными owners и tenants. Подмена только URL ID проверяет object-level authorization; подмена tenant_id в body/header не должна менять trusted principal.
Evidence
Для deny-записи полезны subject ID, action, resource class/ID, tenant, policy version, reason code, request ID и timestamp. Не записывайте password, session ID, raw access token и sensitive resource contents.
Изменение и отзыв прав
Authorization state меняется во времени. Опасный сценарий:
- admin удаляет permission;
- старый self-contained token ещё содержит permission;
- resource server доверяет token до expiry;
- пользователь продолжает действие после revoke.
Варианты решения:
- короткий token lifetime;
- introspection для критичных действий;
- session/token version на account;
- event-driven invalidation с bounded cache;
- повторная проверка свежего состояния для high-impact action.
Trade-off — latency и availability против окна stale authorization. Он должен быть явным для каждой категории действий.
Failure modes и типичные ошибки
- Authentication считается authorization. Любой вошедший пользователь получает доступ к resource.
- Role приходит от клиента. Недоверенный body/header становится policy input.
- Проверяется endpoint, но не object. Возникает IDOR/BOLA.
- Tenant взят из URL без сопоставления с principal. Cross-tenant leak.
- Deny есть не везде. Новый handler остаётся доступным из-за opt-in middleware.
- Token живёт дольше privilege. Revocation не действует до expiry.
- Policy cache не имеет версии и bound. Решения остаются stale неопределённо.
- Секреты попадают в logs. Detection превращается в источник credential theft.
- UI скрывает действие. Прямой API request обходит frontend.
- Policy отказалась — сервис разрешил всё. Неопределённый fail-open разрушает trust boundary.
Ограничения и trade-offs
| Решение | Выигрыш | Цена или риск |
|---|---|---|
| Central policy service | Единые rules и audit | Latency, availability, version rollout |
| Embedded policy | Быстро и локально | Drift между сервисами |
| Self-contained token | Нет lookup на каждый request | Stale claims и revocation window |
| Opaque session | Быстрый централизованный revoke | Stateful store |
| Concealed 404 | Меньше resource enumeration | Сложнее support и диагностика |
| Step-up auth | Сильнее assurance для critical action | Friction и dependency на verifier |
Практическое задание
Задача: реализуйте и протестируйте policy для одного tenant-scoped resource.
Требования:
- principal создаётся только из проверенной session/token;
- action задан явно;
- resource загружается с owner и tenant;
- default — deny;
- есть минимум восемь matrix tests, включая cross-tenant и revoked permission;
- deny создаёт audit без secrets;
- описано поведение при недоступности policy dependency.
Критерии готовности: ни один request parameter не становится доверенным identity attribute; изменение ID, tenant, role и stale token не приводит к allow.
Подсказка
Начните с функции can(subject, action, resource, context). HTTP handler должен лишь получить trusted inputs, вызвать policy и преобразовать decision в response.
Проверка знаний
ИНТЕРАКТИВНАЯ ПРОВЕРКА
Проверка знаний
0 / 4
01Запрос не содержит действительных credentials. Какой HTTP-ответ обычно уместен?
02Какие данные нужны для предметного authorization decision?
03Успешная authentication автоматически разрешает чтение любого ресурса.
04Восстановите безопасный путь запроса к заказу.
- Загрузить resource и security attributes
- Проверить credential/session и получить principal
- Выполнить business action
- Оценить policy для action и resource
Самопроверка
- Чем identifier отличается от authenticator и principal?
- Почему валидная подпись token не гарантирует актуальные permissions?
- Когда 404 вместо 403 оправдан?
- Какие данные нельзя писать в authorization audit?
- Как проверить object-level authorization?
Ответы и критерии
- Identifier обозначает identity, authenticator доказывает контроль, principal — доверенный результат authentication в контексте запроса.
- Claims отражают состояние на момент issuance и могут жить после revoke или смены role.
- Когда раскрытие существования resource само создаёт риск; внутри причина deny всё равно должна быть наблюдаема.
- Raw passwords, session IDs, access/refresh tokens, private keys и sensitive contents.
- Создать resources разных owners/tenants и выполнить negative requests с валидной authentication, меняя object ID и context.
Краткое резюме
- Authentication доказывает контроль authenticator; authorization разрешает конкретное действие.
- Principal формируется только после полной проверки credential/session.
- Policy оценивает subject, action, resource и context, default — deny.
- Object-level и tenant-level проверки выполняются на сервере для каждого запроса.
- Session/token model должна включать expiry, revocation, failure mode и audit.
Следующие шаги
Продолжите отдельной статьёй «Авторизация», затем изучите RBAC, ABAC, sessions и JWT и примените их к уязвимостям IDOR/BOLA.
Источники
- NIST SP 800-63-4 — digital identity model и разделение proofing, authentication, federation и authorization inputs.
- NIST SP 800-63B-4 — authentication assurance, authenticators, session management и lifecycle.
- RFC 9110 — semantics authentication challenge, 401, 403 и возможность скрыть запрещённый ресурс через 404.
- OWASP Authorization Cheat Sheet — least privilege, deny by default, server-side checks каждого запроса и authorization testing.