{"slug": "your-cognito-login-code-fails-because-the-app-client-never-allowed-that-auth", "title": "Your Cognito Login Code Fails Because the App Client Never Allowed That Auth Flow", "summary": "A developer's AI assistant generated Cognito login code that fails because the app client's explicit auth flows, client secret, MFA settings, and token validity units are configured in AWS, not in the repository. The developer built Infrawise, an open-source tool that extracts these per-client settings via read-only AWS API calls and exposes them to AI assistants over MCP, preventing such failures.", "body_md": "Your login screen works locally. You point it at the staging user pool and every sign-in comes back as an error before it ever reaches a password check.\n\nThe code your AI assistant wrote calls `InitiateAuth`\n\nwith `AuthFlow: 'USER_PASSWORD_AUTH'`\n\n. Reasonable guess: it is the flow in most Cognito tutorials. But the app client in staging was created by a Terraform module that set `explicit_auth_flows = [\"ALLOW_USER_SRP_AUTH\", \"ALLOW_REFRESH_TOKEN_AUTH\"]`\n\n, and that client also has a secret. So the call fails twice over: the flow is not enabled for this client, and the request is missing `SECRET_HASH`\n\n.\n\nNone of that is visible in your source files. The assistant read your repo, found no answer, and produced the most statistically common Cognito snippet on the internet.\n\nCognito's failure modes are almost all per app client settings, not per user pool settings. Four of them break generated code immediately:\n\n**Allowed auth flows.** `ExplicitAuthFlows`\n\nis a whitelist. If `ALLOW_USER_PASSWORD_AUTH`\n\nis not on it, `USER_PASSWORD_AUTH`\n\nis rejected regardless of whether the username and password are correct. Client A in dev may allow it while client B in prod does not, and the same handler code then works in one environment and not the other.\n\n**Client secret.** A client with `GenerateSecret: true`\n\nrequires every auth call to carry `SECRET_HASH`\n\n, a base64 HMAC-SHA256 of `username + clientId`\n\nkeyed with the client secret. An assistant that does not know a secret exists will never emit that field, and the call fails on secret verification rather than on credentials.\n\n**MFA.** With MFA set to `ON`\n\n, `InitiateAuth`\n\ntypically returns a `ChallengeName`\n\nand a session instead of tokens. Code written against the happy path reads `response.AuthenticationResult.IdToken`\n\n, gets `undefined`\n\n, and throws somewhere three functions away from the actual cause.\n\n**Token validity units.** `AccessTokenValidity: 60`\n\nmeans nothing on its own. With `TokenValidityUnits.AccessToken = 'minutes'`\n\nit is one hour. With `'days'`\n\nit is two months. Refresh logic built on the wrong unit either hammers the token endpoint or lets sessions die.\n\n**OAuth settings.** If you are using the hosted UI instead of direct auth, the client carries its own `AllowedOAuthFlows`\n\n, `AllowedOAuthScopes`\n\n, and `CallbackURLs`\n\n. Build a redirect against a URL that is not in the callback list, or request a scope the client does not allow, and Cognito refuses the request at the authorize endpoint before your app sees anything. An assistant writing the redirect has no way to know the staging client only registered `https://staging.example.com/callback`\n\nwhile your local dev URL was never added.\n\nEvery one of those lives in AWS, not in your repository. Even when the pool is defined in Terraform, the assistant would have to find the right module, resolve the variables, and know which client the running service actually uses. In practice it does not, so it guesses.\n\n[Infrawise](https://github.com/Sidd27/infrawise) extracts this and hands it to your assistant over MCP. The Cognito extractor in `src/adapters/aws/services.ts`\n\nwalks four read-only calls: `ListUserPools`\n\nfor every pool, then per pool `DescribeUserPool`\n\n, then `ListUserPoolClients`\n\n, then `DescribeUserPoolClient`\n\nfor each client. Both listings are paginated with `NextToken`\n\n, so a pool with 80 app clients does not get silently truncated at the first page.\n\nFor each client it keeps exactly the fields that change how you write the call:\n\n```\nclientName, clientId\nauthFlows          <- ExplicitAuthFlows\noauthFlows         <- AllowedOAuthFlows\noauthScopes        <- AllowedOAuthScopes\ncallbackUrls       <- CallbackURLs\ngeneratesSecret    <- !!ClientSecret\naccessTokenValidity, idTokenValidity, refreshTokenValidity\ntokenValidityUnits <- { accessToken, idToken, refreshToken }\n```\n\nNote `generatesSecret`\n\n. `DescribeUserPoolClient`\n\ndoes return the secret value, and infrawise converts it to a boolean at the point of extraction. The value is never stored in the graph, never cached, and never returned by any tool. Your assistant learns that a secret exists and that `SECRET_HASH`\n\nis mandatory, without ever seeing the secret. Same for users: infrawise never calls any user API. Sign-in code is what it helps you write, not a directory it reads.\n\nThe `get_cognito_overview`\n\nMCP tool returns the whole thing:\n\n```\n{\n  \"total\": 1,\n  \"note\": \"Client secret values and user data are never included.\",\n  \"userPools\": [\n    {\n      \"name\": \"app-users-staging\",\n      \"id\": \"ap-south-1_XXXXXXXXX\",\n      \"mfaConfiguration\": \"OPTIONAL\",\n      \"clients\": [\n        {\n          \"clientName\": \"web-spa\",\n          \"clientId\": \"4h1...\",\n          \"authFlows\": [\"ALLOW_USER_SRP_AUTH\", \"ALLOW_REFRESH_TOKEN_AUTH\"],\n          \"oauthFlows\": [\"code\"],\n          \"oauthScopes\": [\"openid\", \"email\"],\n          \"callbackUrls\": [\"https://staging.example.com/callback\"],\n          \"generatesSecret\": true,\n          \"accessTokenValidity\": 60,\n          \"tokenValidityUnits\": { \"accessToken\": \"minutes\" }\n        }\n      ]\n    }\n  ]\n}\n```\n\nThat is the difference between an assistant guessing `USER_PASSWORD_AUTH`\n\nand an assistant writing SRP with a `SECRET_HASH`\n\n, because the whitelist and the secret flag are sitting right there in its context.\n\nThe tool description registered in `src/server/index.ts`\n\ntells the model when to reach for it and when not to: call it before writing any sign-in, sign-up, or token-refresh code; do not call it to look up users or tokens. That last clause matters more than it looks. Tool descriptions are the only thing steering which tool an agent picks, and a tool that sounds like a user directory will get called for the wrong reasons.\n\nCognito is off by default. `infrawise start`\n\nwrites an `infrawise.yaml`\n\nwith `cognito: { enabled: false }`\n\n, because most repos have no Cognito and there is no reason to make an API call for them. Auth work means flipping one key:\n\n```\ncognito:\n  enabled: true\n```\n\nThe IAM policy is four read actions, and nothing else:\n\n```\ncognito-idp:ListUserPools\ncognito-idp:DescribeUserPool\ncognito-idp:ListUserPoolClients\ncognito-idp:DescribeUserPoolClient\n```\n\nThen:\n\n```\ninfrawise start --claude\n```\n\nThat probes your environment, runs the analysis, writes `.mcp.json`\n\nso your editor reconnects on every future launch, and opens Claude Code with all 21 tools available. From then on you just run `claude`\n\n. Results are cached for 24 hours, and `get_infra_overview`\n\nreports a `freshness`\n\nobject with the analysis age and a `stale`\n\nflag so the assistant can tell when it is looking at yesterday's picture.\n\nAsk \"write me a sign-in handler for the staging pool\" and the flow is no longer a guess. The assistant calls `get_cognito_overview`\n\n, sees `ALLOW_USER_SRP_AUTH`\n\nand `generatesSecret: true`\n\n, and writes SRP with a secret hash the first time.\n\nThe bug class here is boring, which is why it eats so much time. Nothing crashes at build time. Types are fine. Tests that mock the Cognito client pass. The failure only shows up against a real user pool, as an exception whose message is about a flow name rather than about the app client that disallowed it, and the fix is a config value you have to go read in a console tab.\n\nCognito is one instance of the general pattern. The information needed to write correct code is split between your repo and your cloud account, and the assistant only has half. Infrawise closes that gap deterministically: no LLM in the extraction path, just SDK calls, AST parsing, and rule-based analyzers producing a graph that MCP tools read from.\n\n`callbackUrls`\n\nand `oauthScopes`\n\nbefore building a hosted UI redirect. A URL that is not registered is rejected at the authorize endpoint.`generatesSecret`\n\nis true, every auth call needs `SECRET_HASH`\n\n. An assistant that does not know a secret exists will never emit it.`AccessTokenValidity`\n\nis meaningless without `TokenValidityUnits`\n\n. 60 is an hour or two months depending on the unit.`cognito: enabled: true`\n\nin `infrawise.yaml`\n\n(it defaults to false) and grant the four `cognito-idp`\n\nread actions.`get_cognito_overview`\n\nbefore writing sign-in, sign-up, or refresh code. It never returns client secret values or user data.", "url": "https://wpnews.pro/news/your-cognito-login-code-fails-because-the-app-client-never-allowed-that-auth", "canonical_source": "https://dev.to/siddharth_pandey_27/your-cognito-login-code-fails-because-the-app-client-never-allowed-that-auth-flow-1hmf", "published_at": "2026-08-02 20:39:22+00:00", "updated_at": "2026-08-02 21:19:03.137830+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "artificial-intelligence"], "entities": ["AWS Cognito", "Infrawise", "Terraform"], "alternates": {"html": "https://wpnews.pro/news/your-cognito-login-code-fails-because-the-app-client-never-allowed-that-auth", "markdown": "https://wpnews.pro/news/your-cognito-login-code-fails-because-the-app-client-never-allowed-that-auth.md", "text": "https://wpnews.pro/news/your-cognito-login-code-fails-because-the-app-client-never-allowed-that-auth.txt", "jsonld": "https://wpnews.pro/news/your-cognito-login-code-fails-because-the-app-client-never-allowed-that-auth.jsonld"}}