A case study in why the verdict on an architecture decision can shift entirely once you dive into implementation details — using auth security on a large Next.js + AWS system as the vehicle. This article walks the three architectural paths we considered — plus a fourth option that, in this context, won.

Context: a sensitive problem in a large scale system

The system: a large multi-frontend production stack. Multiple Next.js apps spread across subdomains, 100+ backend microservices, AWS Cognito for identity, API Gateway in front of the backend, downstream APIs expecting Authorization: Bearer <id_token> on every request.

The finding: a penetration test flags that our Cognito tokens — ID, access, refresh — are stored in non-HttpOnly cookies. Client-side JavaScript can read them. Any successful XSS exfiltrates them. Any leaked refresh token gives an attacker effectively indefinite access.

Here's what that looks like in flow form — the current client-side API call pattern, with the moment of vulnerability highlighted: