Почему код авторизации Cognito ломается: AI не видит настройки app client в облаке
AI-ассистенты генерируют код для AWS Cognito наугад — потому что настройки 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:
ListUserPools— перечисляет все пулыDescribeUserPool— для каждого пулаListUserPoolClients— перечисляет клиентов пулаDescribeUserPoolClient— для каждого клиента
Оба листинга пагинированы через NextToken, так что пул с 80 app clients не обрежется на первой странице.
Для каждого клиента извлекаются только те поля, которые влияют на то, как вы пишете вызов авторизации:
clientId,clientNameauthFlows— разрешённые ExplicitAuthFlowsoauthFlows,oauthScopes,callbackUrlsgeneratesSecret— приведён к booleanaccessTokenValidity,idTokenValidity,refreshTokenValiditytokenValidityUnits— единицы для каждого типа токена
Обратите внимание на 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. Инструмент никогда не возвращает значения клиентских секретов и данные пользователей.