Ошибка аутентификации при обращении к API обычно означает, что сервер не получил корректные учетные данные или не смог их проверить. Код 401 Unauthorized появляется при отсутствии токена, неверном формате заголовка, истекшем access token, неправильном API-ключе или ошибке проверки подписи. Код 403 Forbidden означает, что учетные данные приняты, но у клиента нет права на конкретную операцию.
Начните диагностику с четырех проверок: срок действия токена, заголовок Authorization, выданные scopes и путь прохождения запроса через proxy, WAF или CDN. В распределенной системе добавьте проверку кэша и синхронизации между экземплярами приложения. Организационные настройки аккаунта тоже влияют на доступ: если сервис зарегистрирован на почту уволившегося сотрудника, восстановление может оказаться недоступным.
Ниже приведен практический алгоритм для API-ключей, Bearer-токенов и OAuth 2.0. Он помогает отделить ошибку учетных данных от недостатка прав, проблем передачи запроса и сбоев инфраструктуры.
Типы аутентификации в API: ключи, токены, OAuth 2.0
Перед исправлением ошибки определите, какой механизм использует API. Формат запроса, срок жизни секрета, способ обновления и набор прав зависят от выбранной схемы.
| Механизм | Что передается | Типичный сбой |
|---|---|---|
| API-ключ | Статичный секрет в заголовке или параметре | Ключ удален, просрочен, ограничен по IP или передан не в том поле |
| Bearer-токен | Временный access token в заголовке | Токен истек, поврежден или имеет неверную подпись |
| OAuth 2.0 | Access token, refresh token и scopes | Закончился access token, отозван refresh token или не хватает scopes |
| Basic | Закодированная пара логин и пароль | Неверные учетные данные, неподходящая схема или отсутствие TLS |
API-ключи: простота и ограничения
API-ключ хранит идентификатор клиента и секрет в одной строке. Сервер может принимать его в заголовке X-API-Key, в другом заголовке из документации или в query string. Ключ часто не имеет встроенного срока действия, поэтому его нужно отзывать и перевыпускать вручную.
curl -sS "$API_URL/resource" -H 'X-API-Key: $API_KEY'
Пустой заголовок, пробел в начале или конце значения, перепутанный ключ тестовой и рабочей среды приводят к отказу. Проверьте регистр имени заголовка, хотя HTTP-серверы обычно считают имена заголовков нечувствительными к регистру. Отдельные шлюзы могут применять собственные правила.
Передача ключа в URL создает лишние риски. Query string попадает в access-логи, историю прокси, трассировки и иногда в заголовок Referer. Длинный ключ вместе с большим набором параметров способен превысить лимит URI. Храните секрет в заголовке, если API это поддерживает.
Для сервисов, которые обращаются к нескольким моделям через единый API, заранее проверьте, какой ключ используется в клиентской библиотеке и где задается endpoint. Например, AiTunnel предоставляет единый интерфейс для доступа к разным ИИ-моделям, поэтому при диагностике важно различать ключ агрегатора и ключ конкретного провайдера.
Bearer-токены и JWT: структура и срок жизни
Bearer-токен передают как доказательство права доступа: сервер принимает того, у кого есть строка токена. Базовый формат заголовка выглядит так:
Authorization: Bearer <token>
Частый вариант Bearer-токена, JWT, состоит из трех частей: header.payload.signature. Первая часть описывает алгоритм и тип токена, вторая содержит claims, третья подтверждает, что содержимое подписал доверенный ключ. Части разделены двумя точками.
В payload часто встречаются поля exp, iat, iss, aud, sub и scope. Claim exp хранит время окончания действия в формате UNIX timestamp. Проверка payload помогает быстро обнаружить просроченный токен, но декодирование не подтверждает подпись. Окончательное решение принимает API или сервер авторизации.
JWT может быть отклонен даже при действующем exp. Причины включают неправильного издателя iss, неподходящую аудиторию aud, неизвестный алгоритм, неверный ключ подписи, отзыв токена и рассинхронизацию времени. Токен одной среды нельзя без проверки использовать против API другой среды.
OAuth 2.0: access token, refresh token, scopes
OAuth 2.0 разделяет получение доступа и обращение к ресурсу. Клиент проходит разрешенный flow, получает access token, передает его API и обновляет через refresh token, когда срок access token заканчивается.
- Клиент запрашивает разрешение с нужными scopes.
- Сервер авторизации выдает access token, а в некоторых flow еще и refresh token.
- Клиент отправляет access token в заголовке
Authorization. - При истечении access token клиент обращается к token endpoint с refresh token.
- Если refresh token отозван или истек, требуется новый интерактивный вход либо повторная выдача доступа.
Access token предназначен для запросов к API. Refresh token обычно хранится дольше, поэтому его нельзя отправлять в каждый запрос к ресурсу или помещать в клиентский JavaScript без продуманной модели защиты. Scopes ограничивают операции: например, токен может разрешать чтение, но запрещать изменение данных. Валидный access token с неподходящим scope обычно приводит к 403 Forbidden.
Проверка срока жизни и валидности токена
Проверяйте время действия токена на том хосте, где выполняется запрос. Часы клиента и сервера должны быть синхронизированы: разница даже в несколько минут способна вызвать отказ, если срок действия короткий и сервер учитывает допустимый clock skew.
Как декодировать JWT и проверить exp
Для локальной проверки передайте токен в переменную окружения и выведите payload через Node.js. Секрет не попадет в историю команд, если значение задается из защищенного хранилища процесса.
TOKEN=$ACCESS_TOKEN node -e 'const p=process.env.TOKEN.split(`.`)[1]; console.log(JSON.parse(Buffer.from(p, `base64url`).toString(`utf8`)));'
Проверьте значение exp в UNIX-времени:
date +%s
Если текущее число больше exp, access token истек. Когда exp отсутствует, поведение зависит от сервера: один API принимает такой токен только при успешной серверной проверке, другой отклоняет его как неполный.
Проверьте остальные claims:
issдолжен соответствовать провайдеру, который выпустил токен;audдолжен содержать идентификатор нужного API;nbfне должен указывать время в будущем;scopeилиpermissionsдолжны содержать право на нужную операцию;- алгоритм в header должен поддерживаться сервером, а ключ подписи должен быть актуальным.
Не вставляйте полный JWT в тикет, чат или журнал диагностики. Для коллег достаточно передать код ответа, время запроса, идентификатор трассировки и обезличенные claims. Практика чтения журналов Linux, приложений, reverse proxy и IdP собрана в пошаговом разборе логов при ошибках аутентификации.
Использование refresh token для обновления доступа
Типовой запрос к token endpoint использует форму application/x-www-form-urlencoded:
curl -sS -X POST "$TOKEN_ENDPOINT" -H 'Content-Type: application/x-www-form-urlencoded' --data-urlencode 'grant_type=refresh_token' --data-urlencode "refresh_token=$REFRESH_TOKEN" --data-urlencode "client_id=$CLIENT_ID"
Конкретный набор параметров зависит от провайдера. Некоторые серверы требуют client_secret, а некоторые запрещают его для публичных клиентов. Секрет клиента передавайте способом, который описан в настройках провайдера, и не добавляйте его в URL.
После обновления сохраните новый access token и его срок действия атомарно. Проверьте, возвращает ли сервер новый refresh token. При ротации refresh token старое значение может сразу стать недействительным. Если два экземпляра приложения одновременно обновляют один токен, один из них способен получить отказ после ротации.
Ошибка обновления не всегда означает проблему API. Частые причины: неверный grant_type, refresh token выдан другому клиенту, изменился redirect или scope, учетная запись заблокирована, refresh token отозван, либо запрос ушел не на тот token endpoint. Код 401 на самом resource endpoint и ошибка на token endpoint нужно расследовать раздельно.
Правильный формат заголовка Authorization
Сначала сравните фактический HTTP-запрос с документацией API. Проверяйте метод, адрес, заголовки и тело отдельно. Секрет может присутствовать в переменной приложения, но не попасть в исходящий запрос из-за ошибки конфигурации клиента.
Схема Bearer: синтаксис и частые ошибки
Корректный заголовок содержит название схемы, один пробел и значение токена:
Authorization: Bearer eyJhbGciOi...
Типовые ошибки:
- отсутствует слово
Bearer; - вместо access token передан refresh token или API-ключ;
- между схемой и токеном нет пробела или добавлены невидимые символы перевода строки;
- токен обрезан при чтении из Secret, переменной окружения или файла;
- клиент отправляет заголовок в redirect-запросе, хотя библиотека удаляет его при смене домена;
- запрос уходит через proxy, который удаляет или переписывает
Authorization; - используется токен другой аудитории или другой среды.
Проверяйте наличие заголовка на безопасном уровне логирования. Логи должны показывать имя заголовка и факт его передачи, но скрывать значение после первых нескольких символов или полностью заменять его маской.
Другие схемы: Basic, API key в заголовке
Схема Basic передает логин и пароль в виде Base64. Base64 не шифрует данные, поэтому Basic допустим только поверх TLS:
ENCODED=$(printf '%s' "$USER:$PASSWORD" | base64 -w 0)
curl -sS "$API_URL/resource" -H "Authorization: Basic $ENCODED"
API-ключ часто передают отдельным заголовком:
curl -sS "$API_URL/resource" -H "X-API-Key: $API_KEY"
Не объединяйте схемы без указания в документации. Заголовок Authorization: Bearer с API-ключом внутри не превращает ключ в OAuth-токен. Аналогично, добавление X-API-Key к запросу с неверным Bearer-токеном не исправляет ошибку проверки токена.
Сравните отправленный запрос с рабочим примером по пяти признакам: имя заголовка, схема, отсутствие лишних кавычек, отсутствие переноса строки и точное значение секрета. В curl переменная должна раскрываться внутри двойных кавычек, если в значении возможны специальные символы.
Права доступа (scopes) и ошибка 403
403 Forbidden появляется после успешной или частично успешной проверки личности, когда политика API запрещает операцию. Сервер может требовать scope, роль, permission, принадлежность к проекту, разрешенный IP или право на конкретный ресурс.
Как проверить scopes в токене
В JWT права часто представлены строкой scope или массивом permissions:
{"scope":"users:read reports:read","permissions":["invoice.read"]}
Сопоставьте эти значения с требованием конкретного endpoint. Scope users:read не дает права на users:write, а право на чтение проекта не обязательно разрешает удаление проекта.
Opaque access token нельзя расшифровать локально. В этом случае используйте предусмотренный провайдером endpoint проверки токена, административную консоль или логи сервера авторизации. Не пытайтесь подбирать scopes по названию endpoint: точное требование задает политика API.
Проверьте, не изменились ли scopes после обновления приложения. Клиент может успешно получить токен, но запросить сокращенный набор разрешений из-за неверной конфигурации, отсутствия согласия пользователя или запрета администратора.
Запрос токена с нужными scopes
При запросе OAuth 2.0 указывайте scopes в поле scope, если выбранный flow и провайдер это поддерживают:
scope=users:read reports:read
Запрошенные права не гарантируют выдачу. Сервер может удалить часть scopes, если клиенту запрещен доступ, пользователь не дал согласие или политика организации ограничивает разрешения. Сравните запрошенные и фактически выданные scopes в ответе token endpoint.
Если токен имеет нужный scope, но API все равно возвращает 403, проверьте роль пользователя, состояние проекта, региональные ограничения, принадлежность ресурса и правила IP-доступа. Посмотрите тело ответа: поле insufficient_scope, permission_denied или внутренний код политики существенно сужает поиск причины.
Инфраструктурные причины: CDN, WAF, reverse proxy
Запрос проходит через несколько уровней: клиент, DNS, балансировщик, CDN, WAF, reverse proxy и application server. Каждый компонент может изменить заголовки, ограничить размер URI, заблокировать подозрительный шаблон или применить собственную политику доступа.
Прямой вызов backend способен работать, а публичный адрес возвращать 401, 403 или 414. Сравнивайте не только код, но и заголовки ответа, тело, время обработки и идентификатор запроса. Логи CDN и WAF покажут, дошел ли запрос до приложения.
Ошибка 414 и слишком длинный URI
Код 414 Request-URI Too Long означает, что URI превышает допустимую длину. Сам по себе этот код не подтверждает ошибку аутентификации, но часто появляется рядом с ней, когда секрет или большой набор параметров передают через query string.
Причины длинного URI:
- API-ключ, токен или персональные данные передаются в URL;
- клиент ошибочно использует GET вместо POST и помещает большое тело в параметры;
- redirect зациклился и каждый переход добавляет параметры;
- приложение сериализует состояние сессии в query string;
- лимиты CDN, WAF, proxy и backend отличаются.
Используйте заголовок для API-ключа или Bearer-токена. Для больших данных выбирайте POST, PUT или PATCH согласно контракту API. Уберите секреты из URL, потому что они остаются в журналах даже после успешного запроса.
Диагностика: сравнение прямого доступа и через gateway
- Сохраните время запроса, HTTP-код, метод, путь без секрета и идентификатор трассировки.
- Повторите тот же запрос напрямую к backend, если сетевой доступ и политика безопасности это разрешают.
- Сравните, дошел ли заголовок
Authorizationдо приложения. - Проверьте лимиты размера URI и заголовков на CDN, WAF, reverse proxy и application server.
- Изучите правила WAF: блокировка может срабатывать на шаблон параметра, необычный User-Agent или частоту запросов.
- Сопоставьте access-лог gateway с журналом приложения по времени и request ID.
Для теста меняйте один параметр за раз. Сначала отправьте минимальный запрос с тем же токеном, затем добавьте query-параметры и тело. Такой порядок показывает, на каком изменении возникает отказ.
При размещении backend и gateway в облачной инфраструктуре проверьте, какой компонент завершает TLS и передает заголовки дальше. Timeweb Cloud предоставляет VDS, серверы, базы данных, хранилище и Kubernetes, поэтому при такой схеме нужно отдельно проверить настройки ingress, балансировщика и приложения.
Кэширование и состояние в распределенных системах
Кэширование токенов способно создавать 401 даже при корректной выдаче новых учетных данных. Проблема появляется, когда один экземпляр приложения обновил токен, а другой продолжает использовать старое значение из локального кэша.
Пример: кэширование JWT и волна 401
Представьте кластер из трех экземпляров. Приложение хранит JWT в локальном кэше 10 минут, хотя access token действует 5 минут. После истечения токена экземпляр A получает новый токен, а экземпляры B и C продолжают отправлять старый. При росте нагрузки доля 401 быстро увеличивается, хотя обновление на одном узле прошло успешно.
Возможны и другие race condition:
- два worker одновременно обнаруживают истечение срока и оба обновляют токен;
- один worker записывает новый access token, а другой возвращает устаревшее значение;
- кэш содержит токен для другого client ID или tenant;
- после ротации refresh token старое значение остается в памяти процесса;
- локальный кэш и общий Memcached хранят разные версии состояния.
Общий Memcached не устраняет гонку автоматически. Он уменьшает расхождение между узлами, но не заменяет блокировку, проверку версии, TTL и обработку одновременного обновления.
Как избежать проблем с кэшем токенов
- Храните access token не дольше его фактического срока действия и закладывайте небольшой запас по времени.
- Связывайте запись кэша с client ID, tenant, audience и scopes.
- Используйте централизованное хранилище, если состояние должно быть общим для всех экземпляров.
- Добавьте распределенную блокировку или механизм single-flight, чтобы один запрос обновлял токен, а остальные ждали результат.
- Удаляйте старое значение после ответа 401 и повторяйте запрос ограниченное число раз, например один раз.
- Разделяйте кэширование публичных данных и секретов. Не кэшируйте токены в HTTP-кэше.
- Логируйте fingerprint токена, например хэш, а не сам секрет. Это помогает увидеть смену значения без утечки.
Повтор запроса после 401 должен иметь предел. Бесконечное обновление токена превращает ошибку доступа в цикл нагрузки на token endpoint и может привести к блокировке клиента.
Безопасное управление токенами и ключами
Ошибки доступа часто возникают из-за процесса управления аккаунтами, а не из-за кода запроса. Для каждого критичного API зафиксируйте владельца, технического администратора, способ выдачи ключа, срок ротации и порядок восстановления.
MFA для критичных систем
Многофакторную аутентификацию применяйте для внешних приложений, удаленного и административного доступа, если сервис ее поддерживает. Для привилегированных аккаунтов выбирайте методы, устойчивые к фишингу, например аппаратные ключи безопасности или WebAuthn, когда их принимает провайдер.
MFA защищает административную учетную запись, через которую выпускают API-ключи, меняют scopes и отзывают refresh token. Оно не исправляет неверный заголовок и не продлевает access token, но снижает риск захвата панели управления.
Практические варианты TOTP, Push, U2F и WebAuthn, включая корпоративные сценарии, разобраны в руководстве по выбору метода MFA. Для привилегированных учетных записей учитывайте рекомендации CISA по фишинг-устойчивой MFA и контролям доступа, описанным в CIS Controls v8.1.
Избегайте единой точки отказа при восстановлении
Сервис, зарегистрированный на личную почту одного сотрудника, создает операционный риск. После увольнения или потери телефона письмо для сброса доступа может остаться недоступным, а команда потеряет возможность перевыпустить API-ключ.
Для критичных интеграций:
- используйте корпоративный аккаунт или управляемую группу;
- назначайте минимум двух администраторов с раздельными учетными данными;
- храните резервные коды и инструкции в защищенном хранилище;
- проверяйте, кто получает письма о сбросе и уведомления о ротации;
- документируйте процедуру отзыва и выпуска ключа без публикации самих секретов;
- проводите проверку восстановления по расписанию, например раз в квартал.
Не привязывайте единственный канал восстановления к телефону или почте одного человека. Для сценариев с потерянным ключом или телефоном используйте протокол восстановления доступа 2FA для администратора, адаптируя шаги под конкретный IdP и внутреннюю политику.
Заключение: чек-лист для быстрой диагностики
Пройдите проверки в таком порядке:
- Зафиксируйте HTTP-код:
401указывает на проблему проверки учетных данных,403чаще связан с правами,414указывает на слишком длинный URI. - Уточните схему аутентификации: API-ключ, Bearer, Basic или OAuth 2.0.
- Проверьте, что секрет передается в нужном заголовке и не содержит лишних кавычек, пробелов или перевода строки.
- Для JWT сравните
expс текущим UNIX-временем и проверьтеiss,aud,nbfи подпись на стороне сервера. - Если access token истек, запросите новый через refresh token. Проверьте ротацию и одновременное обновление в нескольких worker.
- Сравните фактически выданные scopes с требованием endpoint. При 403 проверьте роли, permissions и ограничения проекта.
- Повторите минимальный запрос напрямую к backend и через gateway, затем сравните логи CDN, WAF, reverse proxy и приложения.
- Уберите токены и ключи из query string. Проверьте лимиты URI, если появляется 414.
- Очистите устаревший кэш токенов, проверьте TTL, race condition, stale cache и согласованность Memcached или другого общего хранилища.
- Убедитесь, что владелец аккаунта, администраторы и каналы восстановления доступны команде.
- После исправления отзовите скомпрометированные ключи, выпустите новые и проверьте MFA для привилегированных аккаунтов.
Такой порядок сокращает область поиска: сначала проверяются данные запроса и срок токена, затем права, инфраструктура, состояние кэша и управление аккаунтом. Сохраняйте в отчетах только технические признаки сбоя, без access token, refresh token, API-ключей и паролей.