Назад в блог

Авторизация MCP: OAuth 2.1, PKCE и как не отдать чужой токен

Как работает авторизация MCP по спеке 2026: OAuth 2.1 + PKCE, Protected Resource Metadata, Resource Indicators и audience. Почему token passthrough — главный антипаттерн.

Иллюстрация к материалу: Авторизация MCP: OAuth 2.1, PKCE и как не отдать чужой токен

Ещё год назад MCP-сервер можно было запустить с Bearer sk-... и считать задачу решённой. С ревизии 2025-11-25 это перестало быть опцией: любой MCP-сервер с публичным URL обязан реализовать OAuth 2.1 с PKCE, а спецификация 2026-07-28 сделала стек ещё строже — discovery, audience, обмен токенами. Разберём, как это устроено и где команды чаще всего ошибаются.

Почему API key больше не достаточно

Спецификация MCP прямо формулирует правило: STDIO на localhost — можно с ключом из окружения, HTTP в интернете — только OAuth 2.1 с PKCE S256. Метод plain запрещён, S256 обязателен даже для confidential-клиентов. Причина — перехват кода авторизации без привязки запроса к обмену.

Это не «рекомендация», а условие соответствия. Сервер без OAuth 2.1 + PKCE — non-compliant, и сканеры безопасности уже это подсвечивают. Модель «вставил API key и забыл» не даёт ни персонификации, ни scope, ни отзыва.

MCP — resource server, а не IdP: три роли

Главная ментальная модель, которая ломается:

  • MCP-клиент (рантайм агента) — получает токен;
  • authorization server (ваш IdP: Keycloak, Auth0, Cognito) — аутентифицирует пользователя и выпускает токен;
  • MCP-сервер — только валидирует токен на каждый запрос и решает, что разрешить.

MCP-сервер не хранит пароли, не показывает логин-страницу и не выпускает токены сам. Его единственная работа в безопасности — проверить токен и отказать всему, что не для него. Если сервер сам «выдаёт» токены без внешнего IdP — это уже не MCP-авторизация.

Discovery: Protected Resource Metadata (RFC 9728)

Как клиент узнаёт, куда идти за авторизацией? MCP-сервер публикует документ Protected Resource Metadata по пути /.well-known/oauth-protected-resource. Клиент читает его до любой аутентификации и узнаёт три вещи: какому authorization server доверяет сервер, какие scope он понимает и свой канонический идентификатор ресурса.

Сервер обязан отдавать PRM, клиент обязан его использовать для discovery — это не опционально. На практике это даёт zero-touch: агент динамически узнаёт требования без хардкода. В ответе 401 сервер указывает WWW-Authenticate: Bearer resource_metadata="..." — и клиент идёт по этой ссылке.

Дальше клиент делает discovery самого authorization server через RFC 8414 или OpenID Connect Discovery и получает authorization_endpoint, token_endpoint, registration_endpoint.

Audience и Resource Indicators (RFC 8707) — против confused deputy

Валидный, неистёкший, правильно подписанный токен — ещё не повод пускать. Сервер обязан проверить, что токен выдан именно ему — поле aud должно содержать его канонический идентификатор.

Клиент при запросе токена передаёт resource (индикатор ресурса) и на этапе авторизации, и на этапе обмена кода. Authorization server вшивает это в audience токена. Сервер отклоняет любой токен с чужим aud, даже если подпись верна. Без этой проверки токен, утёкший с одного сервиса, становится мастер-ключом к другому — классический confused deputy.

С ревизии 2026-07-28 Resource Indicators обязательны для клиентов, а не «рекомендованы».

Token passthrough — главный антипаттерн

Самый опасный антипаттерн — принять токен от клиента и переслать его дальше в downstream-сервис как есть. Спецификация запрещает это прямо.

Почему это дыра:

  • downstream считает, что токен валидировал MCP-сервер;
  • в логах теряется атрибуция — чей это был вызов;
  • аудитор не может восстановить цепочку.

Правильные варианты:

  • Прямая валидация — MCP-сервер валидирует токен напрямую у authorization server (или по JWKS) и не форвардит его;
  • Token Exchange (RFC 8693) — сервер обменивает входящий токен на downstream-токен с нужным audience и scope. Обмен логируется у IdP и даёт узкий токен для конкретного сервиса.

Правило простое: «никогда не форвардь токены через MCP-сервер в бэкенды, валидируй напрямую, для downstream — обмен».

Что проверить перед продом

Чек-лист, который ловит 90% ошибок:

  • PKCE S256 обязателен, plain отклоняется;
  • PRM отдаётся и содержит authorization_servers, scopes_supported, resource;
  • на 401 возвращается WWW-Authenticate с Bearer + resource_metadata и по возможности scope;
  • клиент передаёт resource на обоих этапах и проверяет iss в ответе (RFC 9207);
  • сервер проверяет aud == своему идентификатору на каждый запрос;
  • Dynamic Client Registration — только если нужен fallback; приоритет — Client ID Metadata Documents (URL как идентичность клиента, с 2026 — предпочтительно);
  • refresh_token хранится конфиденциально, scope при refresh пробрасывается для провайдеров вроде Azure AD;
  • на 403 insufficient_scope сервер возвращает WWW-Authenticate с нужным scope, клиент делает step-up, а не просит всё подряд.

Если любой из этих пунктов не реализован — сервер формально не соответствует спеке и практически уязвим.

Где Codenik: управляемый слой для MCP-авторизации

MCP-авторизация — не то, что хочется писать руками для каждого сервера. Codenik закрывает её как управляемый слой: публикует PRM, держит discovery, валидирует audience, применяет секреты через Token Exchange вместо passthrough и пишет аудит каждого обмена.

Команда получает least-privilege scope из WWW-Authenticate и scopes_supported без ручного хардкода, а агент — zero-touch открытие: узнал требования динамически, прошёл PKCE, получил узкий токен для конкретного ресурса. Подробнее про секреты без попадания в контекст — в статье «MCP без локальных секретов».

Короткий вывод

Авторизация MCP в 2026 — это OAuth 2.1 + PKCE S256 + Protected Resource Metadata + Resource Indicators + Token Exchange для downstream. MCP-сервер — resource server, который валидирует audience на каждый запрос и никогда не форвардит чужие токены.

Сделайте discovery, audience и обмен токенами с первого дня — и вы закроете и соответствие спеке, и самый частый вектор атаки MCP.

Источники и дальнейшее чтение