On a recent software development project that already planned to use AWS, we used AWS Cognito for authentication. Cognito is Amazon’s managed identity platform for web and mobile
apps, offering features like MFA, password reset flows, and sign-in.
On paper, it’s a strong fit for projects already using AWS. In practice, the rough edges cost real time, and the documentation rarely pointed us to the root cause. Here are three gotchas we hit and how we solved them.
1. MFA QR Regeneration Locks You Out
During MFA setup, a dialog fetched time-based one-time password (TOTP) data and displayed the returned QR code on mount. If navigating back through the dialog or if the dialog re-rendered, the fetch would fire twice, sending two concurrent `AssociateSoftwareTokenCommand` requests to Cognito. The second one errored with:
Failed to create resource — Only one request to update user MFA settings can be processed at a time.
The original code fetched TOTP data imperatively inside an effect — uncached and
vulnerable to duplicate requests. To resolve the issue, the request was moved to a cached React Query hook, keyed on the MFA flow, so concurrent fetches get deduped and the result is reused across the dialog.
In short, Cognito’s TOTP MFA endpoints aren’t idempotent-friendly. If you’re calling them from a React component, the data should be treated as a query, and the query cache should be utilized to enforce single-flight behavior.
2. Maintaining Two Auth Tokens
When authenticating with Cognito, the client receives a JWT containing an ID token, an access token, and a refresh token. The access token is required to gain access to resources in Cognito, and the ID token is merely a way to verify the user’s identity. In order to map the logged-in Cognito user to the user in the app, we would normally have added a custom attribute to the access token, so we would not have to maintain both the ID token and the access token.
However, unlike other providers like Auth0 and Firebase, Cognito does not readily support adding custom attributes to their access token. In order to add these custom attributes, a Lambda function must be set up to intercept the action and append these values to the access token. Instead of going with this approach, we ended up accepting this tradeoff and decided to maintain both authentication tokens.
While it’s not a dealbreaker, it’s worth noting that Cognito does not make it easy to set up custom attributes on access tokens. Given that other auth providers do handle this, I expected that Cognito would too.
3. Default Email Verification
Creating a Cognito user triggers AWS’s built-in verification flow, which means users receive an unbranded one-time code directly from AWS. Not great for a polished product experience, and confusing for users who don’t know what “AWS” has to do with the app they just signed up for.
The fix is to either to mark user’s emails as verified on sign up or to intercept the email trigger with a Lambda function and send a verification email through your own branded email template. Easy once you know it, but the issue wasn’t immediately visible when we started setting up users.
Would We Pick Cognito Again?
For projects where AWS services are already heavily use and where tight IAM integration matters, probably yes. However, the documentation assumes you already know how Cognito thinks, and the defaults often do not match typical product requirements. If choosing to use Cognito, it’s important to budget extra time to iron out all the unexpected wrinkles that may pop up.