Перейти к содержанию

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

Сессия связала браузер с пользователем u17. Но API могут использовать и редакторский скрипт, и другой программный клиент, которому не нужен интерактивный браузерный вход. Теперь зададим два поддерживаемых способа предъявления credentials и согласуем отказ, когда личность не подтверждена.

Результат урока — клиент различает публичное чтение, защищённое действие и необходимость входа. Мы не создаём парольный сервис, выдачу токенов или OAuth-поток. Их доверенный результат предполагается отдельно. Учебные sid-alice и tok-alice — обозначения, которые нельзя использовать как настоящие секреты.

Две альтернативы

Браузерный интерфейс использует сессионную cookie из предыдущего урока. Программный клиент может предъявить непрозрачный Bearer-токен в Authorization. Оба способа после проверки связываются с одним subject u17 и его актуальной областью, но не являются одной строкой в разных местах.

GET /api/me HTTP/1.1
Host: catalog.example
Authorization: Bearer tok-alice
Accept: application/json, application/problem+json

Сервис проверяет токен: действительность, срок, соответствие этому API и область. В нашей модели токен хранит или ссылается на серверные разрешения. Он не обязательно является JWT. Формат Bearer определяет способ предъявления, а не структуру содержимого. Bearer в HTTP.

Bearer означает, что обладатель пригодного токена способен использовать его без отдельного доказательства владения ключом. Поэтому раскрытая строка опасна. Токен не размещают в query ради удобства ссылки и не печатают целиком в журнале. HTTPS защищает передачу, но не исправляет ошибку копирования в публичный отчёт.

Мы допускаем ровно один режим в защищённом запросе. Если одновременно есть сессионная cookie и Authorization Bearer, сервер возвращает 400 ambiguous-credentials. Это прикладная политика против неявного выбора личности: один обработчик не должен взять cookie Alice, другой токен Bob, а журнал записать произвольного третьего пользователя.

Отсутствие credentials

Публичный GET /api/courses/c001 не требует входа и возвращает те же общедоступные поля. Защищённый /api/me без пригодного основания даёт 401. Смысловой ответ:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="catalog"
Content-Type: application/problem+json
Cache-Control: no-store

{"type":"https://catalog.example/problems/authentication-required","title":"Нужно подтвердить личность","status":401}

401 требует применимого challenge. Здесь Bearer действительно является поддерживаемой альтернативой для этого ресурса. Cookie не становится Bearer из-за такого поля: браузерный интерфейс имеет свой процесс входа и понимает ответ как необходимость получить действующую сессию. Стандартное поле описывает доступный способ аутентификации API, а не автоматическую форму HTML.

Наш API не перенаправляет JSON-клиента на страницу входа. Иначе клиент мог бы получить HTML с 200 после автоматического перехода и ошибиться при JSON-разборе. Известный 401 сохраняет машинный результат, а пользовательский интерфейс сам решает, когда показать вход и как сохранить незавершённое намерение.

Название статуса исторически содержит Unauthorized, но в договоре важно различить подтверждение личности и проверку права. Пользователь может быть успешно распознан и всё равно не иметь разрешения на course c003. Такой случай станет 403, а не бесконечным предложением ввести пароль заново.

Непригодный токен

Если клиент предъявил истёкший Bearer, ответ содержит соответствующую причину challenge:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="catalog", error="invalid_token"
Content-Type: application/problem+json
Cache-Control: no-store

{"type":"https://catalog.example/problems/invalid-credential","title":"Основание входа недействительно","status":401}

При отсутствии токена не нужно сообщать invalid_token так, словно сервер проверил присланное значение. Причина должна соответствовать фактическому входу. Подробности вроде внутреннего ключа хранилища или полного токена не выводятся в problem detail.

Для истёкшей cookie-сессии используем общий 401 с Bearer challenge без утверждения, что cookie являлась невалидным Bearer. Интерактивный клиент получает новую сессию через свой вход, а скрипт обрабатывает свой токен по отдельной процедуре. Механизм обновления credentials здесь не реализован и не должен угадываться из одного кода.

Клиент не повторяет бесконечно прежний запрос после 401. Старое непригодное основание не станет действительным от десяти быстрых попыток. Сначала требуется разрешить состояние входа, затем осознанно восстановить предметное действие. Особенно важно сохранить прежний Idempotency-Key создания, если его исход был неопределённым до обновления credentials.

Браузер выбирает режим передачи

Cookie может отправляться автоматически в подходящем контексте. Bearer программный клиент добавляет явно. Если браузерная программа использует Bearer и не хочет смешивать его с автоматически выбранной сессией, ей нужно исключить cookies через соответствующий режим credentials.

const response = await fetch('https://catalog.example/api/me', {
  credentials: 'omit',
  headers: { Authorization: 'Bearer tok-alice' }
});

Это самостоятельная иллюстрация будущего клиента, не выполненный запрос. tok-alice надо заменить результатом реальной разрешённой выдачи, а не скопировать из статьи. Для межorigin браузерного клиента Authorization также участвует в CORS-проверке; наличие правильного токена не отключает политику браузера.

Режим omit не делает токен невидимым серверу: явный Authorization остаётся выбранным основанием. Он исключает автоматическое смешивание cookie в рассматриваемом сценарии. Фактическое поведение библиотеки и origin-контекст будущей программы следует исследовать отдельно при реализации.

Успешный профиль не подтверждает все действия

После пригодного GET /api/me клиент может увидеть id u17, роль editor и список frontend. Это полезно для интерфейса, однако профиль является результатом конкретного чтения в конкретный момент. Он не выдаёт разрешение на любой следующий адрес и не позволяет клиенту самому отменить серверную проверку.

Представим, что форма открылась после успешного профиля, а затем Alice потеряла редакторскую роль. Следующий PUT обязан проверить нынешние условия. Иначе отображённое раньше слово editor станет фактически бессрочным пропуском. Если токен ссылается на серверное состояние, новый запрос должен учитывать отзыв по выбранной политике; если другая система использует автономный токен, ей понадобится собственный честный договор задержки и срока.

Особенно важно отделить пользовательский текст от секретов. Вход «истёк» можно объяснить без вывода всей строки Authorization на экран. Для технического разбора сохраняют безопасный id обращения и причину, а не пригодный Bearer. Копирование токена в сообщение об ошибке создаёт новый канал доступа независимо от того, был ли исходный ответ правильным 401.

Отзыв и права проверяются отдельно

Сервис должен уметь отозвать доступ по выбранной модели токена. Нельзя считать непригодным только синтаксически повреждённый Bearer: вполне правильная строка может истечь, быть отозвана или относиться к другому API. Проверка формы не равна проверке доверия.

После установления subject операция проверяет право на текущий ресурс. Токен не превращает Alice в редактора всех тем автоматически. Даже подписанный набор полей требует проверки доверенной стороны, срока и области; наш непрозрачный пример не заставляет читателя писать собственный JWT-парсер.

Смена credentials не отменяет версию курса. Для PUT по-прежнему нужен If-Match выбранного JSON, а Cookie-режим добавит CSRF-условие. Эти проверки отвечают на разные вопросы: кто действует, что ему разрешено, из какого браузерного контекста пришло изменение и к какой версии оно применимо.

Прикладной ключ повторяемого POST связан с subject, а не с сырой строкой текущего токена. Ротация сессии Alice не должна создавать нового пользователя в registry операций. Повтор с тем же намерением после разрешённого обновления credentials находит прежний результат в установленном 24-часовом окне. Иначе безопасность обновления случайно превратилась бы в создание дубликата.

Теперь можно разобрать три обращения: публичное чтение без входа, личный профиль с пригодным токеном и защищённая операция без него. У каждого есть конкретный ожидаемый результат. Следующий урок добавит предметные права на c001–c004, чтобы аутентифицированная личность не подменяла разрешение любого действия.

Примеры продолжения серии. Оглавление курса.