01.08.2027 335 материалов

Почему код авторизации Cognito ломается: AI не видит настройки app client в облаке

AI-ассистенты генерируют код для AWS Cognito наугад — потому что настройки app client живут в облаке, а не в репозитории. Разбираемся, почему ломается авторизация и как новый инструмент закрывает этот пробел.

Почему код авторизации Cognito ломается: AI не видит настройки app client в облаке

Код авторизации, написанный AI-ассистентом, компилируется, проходит тесты с моками и падает в staging — потому что ни один инструмент не заглянул в реальную конфигурацию app client в AWS.

Проблема, знакомая каждому, кто работал с Cognito

Представьте типичную ситуацию: вы пишете экран входа в приложение. Локально всё работает — форма отображается, поля заполняются, кнопка нажимается. Переключаетесь на staging-окружение, и каждый вызов InitiateAuth возвращает ошибку ещё до проверки пароля.

Что произошло? AI-ассистент, который помогал писать код, вызвал InitiateAuth с параметром AuthFlow: 'USER_PASSWORD_AUTH'. Логичный выбор — этот flow встречается в большинстве туториалов по Cognito. Но app client в staging создан через Terraform-модуль, который указал explicit_auth_flows = ["ALLOW_USER_SRP_AUTH", "ALLOW_REFRESH_TOKEN_AUTH"]. Клиент ещё и с секретом. Итого два провала сразу: нужный flow не включён, а в запросе отсутствует обязательный SECRET_HASH.

Ничего из этого не видно в исходниках проекта. Ассистент прочитал репозиторий, не нашёл ответа и сгенерировал самый статистически частый сниппет из интернета.

App client — это конфигурация, которой нет в вашем репозитории

Ключевая особенность Amazon Cognito, о которой часто забывают: подавляющее большинство сбоев определяется настройками app client, а не user pool. Один и тот же код может работать с клиентом A и падать с клиентом B внутри одного и того же пула.

Перечислю параметры, которые ломают сгенерерированный код моментально.

Разрешённые auth flows. Поле ExplicitAuthFlows — это белый список. Если ALLOW_USER_PASSWORD_AUTH в нём нет, вызов с USER_PASSWORD_AUTH будет отклонён, даже если логин и пароль абсолютно верные. В dev-окружении может быть разрешён один набор flows, в prod — совершенно другой, и один и тот же обработчик ведёт себя по-разному в зависимости от окружения.

Client secret. Если клиент создан с флагом GenerateSecret: true, каждый auth-запрос должен содержать поле SECRET_HASH — это base64-encoded HMAC-SHA256 от строки username + clientId, подписанный секретом клиента. Ассистент, который не знает о существовании секрета, никогда не сгенерирует это поле. Запрос провалится на проверке секрета, а не на проверке учётных данных — и сообщение об ошибке не укажет на причину.

MFA. Когда многофакторная аутентификация включена принудительно (MFA: ON), вызов InitiateAuth обычно возвращает не токены, а ChallengeName и сессию. Код, написанный для «счастливого пути», пытается прочитать response.AuthenticationResult.IdToken, получает undefined и падает в функции, расположенной в трёх вызовах от реальной причины.

Единицы измерения токенов. Значение AccessTokenValidity: 60 без контекста ничего не значит. При TokenValidityUnits.AccessToken = 'minutes' это час, при 'days' — два месяца. Логика обновления токенов, построенная на неверной единице, либо забивает эндпоинт обновления запросами, либо позволяет сессиям истекать незамеченными.

OAuth-настройки. При использовании hosted UI вместо прямой авторизации клиент несёт собственные AllowedOAuthFlows, AllowedOAuthScopes и CallbackURLs. Постройте redirect на URL, которого нет в списке callback, или запросите scope, который клиент не разрешает — Cognito отклонит запрос на эндпоинте авторизации, и ваше приложение даже не узнает об этом. Ассистент, генерирующий redirect, не имеет возможности узнать, что staging-клиент зарегистрировал только https://staging.example.com/callback, а ваш локальный URL туда не добавлен.

Каждый из этих параметров живёт в AWS, а не в вашем репозитории. Даже когда пул определён в Terraform, ассистенту пришлось бы найти нужный модуль, разрешить переменные и знать, какой именно клиент использует запущенный сервис. На практике этого не происходит — ассистент угадывает.

Как извлечь реальную конфигурацию клиента

Инструмент Infrawise решает эту проблему, извлекая конфигурацию Cognito и передавая её ассистенту через MCP (Model Context Protocol). Cognito-экстрактор в проекте выполняет четыре read-only вызова к AWS API:

  1. ListUserPools — перечисляет все пулы
  2. DescribeUserPool — для каждого пула
  3. ListUserPoolClients — перечисляет клиентов пула
  4. DescribeUserPoolClient — для каждого клиента

Оба листинга пагинированы через NextToken, так что пул с 80 app clients не обрежется на первой странице.

Для каждого клиента извлекаются только те поля, которые влияют на то, как вы пишете вызов авторизации:

  • clientId, clientName
  • authFlows — разрешённые ExplicitAuthFlows
  • oauthFlows, oauthScopes, callbackUrls
  • generatesSecret — приведён к boolean
  • accessTokenValidity, idTokenValidity, refreshTokenValidity
  • tokenValidityUnits — единицы для каждого типа токена

Обратите внимание на generatesSecret. Метод DescribeUserPoolClient возвращает и само значение секрета, но Infrawise конвертирует его в boolean на этапе извлечения. Значение секрета никогда не хранится в графе, не кешируется и не возвращается никаким инструментом. Ассистент узнаёт, что секрет существует и SECRET_HASH обязателен, но никогда не увидит сам секрет. Точно так же инструмент не вызывает API для работы с пользователями — он помогает писать код авторизации, а не читать директорию.

Что возвращает get_cognito_overview

Инструмент MCP get_cognito_overview отдаёт структурированный объект с полной конфигурацией:

{
  "total": 1,
  "note": "Client secret values and user data are never included.",
  "userPools": [
    {
      "name": "app-users-staging",
      "id": "ap-south-1_XXXXXXXXX",
      "mfaConfiguration": "OPTIONAL",
      "clients": [
        {
          "clientName": "web-spa",
          "clientId": "4h1...",
          "authFlows": ["ALLOW_USER_SRP_AUTH", "ALLOW_REFRESH_TOKEN_AUTH"],
          "oauthFlows": ["code"],
          "oauthScopes": ["openid", "email"],
          "callbackUrls": ["https://staging.example.com/callback"],
          "generatesSecret": true,
          "accessTokenValidity": 60,
          "tokenValidityUnits": { "accessToken": "minutes" }
        }
      ]
    }
  ]
}

Вот в этом и разница между ассистентом, который угадывает USER_PASSWORD_AUTH, и ассистентом, который пишет SRP с SECRET_HASH — белый список и флаг наличия секрета прямо перед ним в контексте.

Описание инструмента, зарегистрированное в MCP-сервере, объясняет модели, когда к нему обращаться, а когда нет: вызывайте перед написанием кода для sign-in, sign-up или обновления токенов; не вызывайте для поиска пользователей или токенов. Последнее ограничение важнее, чем кажется. Описания инструментов — единственное, что направляет агента при выборе tool, и инструмент, который звучит как директория пользователей, будет вызываться не по назначению.

Как это включить

Cognito-поддержка в Infrawise отключена по умолчанию. Команда infrawise start создаёт файл infrawise.yaml с cognito: { enabled: false }, потому что большинство репозиториев не используют Cognito, и нет причин делать лишние API-вызовы. Для работы с авторизацией достаточно одной настройки:

cognito:
  enabled: true

IAM-политика требует всего четыре разрешения на чтение:

cognito-idp:ListUserPools
cognito-idp:DescribeUserPool
cognito-idp:ListUserPoolClients
cognito-idp:DescribeUserPoolClient

Затем запуск infrawise start --claude — инструмент проанализирует окружение, запишет .mcp.json для автоматического подключения при следующем запуске и откроет Claude Code со всеми доступными инструментами. Результаты кешируются на 24 часа, а вызов get_infra_overview возвращает объект freshness с возрастом анализа и флагом stale, чтобы ассистент понимал, смотрит ли он на вчерашнюю картину.

После этого запрос «напиши обработчик входа для staging-пула» перестаёт быть угадыванием. Ассистент вызывает get_cognito_overview, видит ALLOW_USER_SRP_AUTH и generatesSecret: true и с первого раза генерирует SRP с секретом.

Скучный класс ошибок, который съедает часы

Подобные баги коварны именно своей банальностью. Ничего не падает на этапе сборки. Типы в порядке. Тесты с моками Cognito-клиента проходят. Сбой проявляется только при обращении к реальному пулу — в виде исключения, сообщение которого говорит о имени flow, а не о том, что app client его не разрешает. Исправление — значение конфигурации, которое нужно идти смотреть в консоли AWS.

Cognito здесь — лишь частный случай общей проблемы. Информация, необходимая для написания корректного кода, размазана между репозиторием и облачным аккаунтом. Ассистент видит только половину. Infrawise закрывает этот зазор детерминированным способом: никакого LLM в пути извлечения — только вызовы AWS SDK, парсинг AST и анализаторы на правилах, которые строят граф, читаемый инструментами MCP.

Ключевые выводы

  • Ошибки Cognito определяются настройками конкретного app client, а не user pool. Один и тот же код работает с одним клиентом и падает с другим внутри одного пула.

  • Перед построением redirect через hosted UI проверяйте callbackUrls и oauthScopes. URL, не зарегистрированный в списке callback, будет отклонён на эндпоинте авторизации.

  • Если generatesSecret равен true, каждый auth-запрос требует SECRET_HASH. Ассистент, не знающий о существовании секрета, никогда не сгенерирует это поле.

  • AccessTokenValidity бессмысленен без TokenValidityUnits. Значение 60 — это час или два месяца в зависимости от единицы измерения.

  • Для активации Cognito-поддержки в Infrawise установите cognito: enabled: true в infrawise.yaml (по умолчанию false) и предоставьте четыре read-only разрешения cognito-idp.

  • Вызывайте get_cognito_overview перед написанием кода для sign-in, sign-up или refresh. Инструмент никогда не возвращает значения клиентских секретов и данные пользователей.