Dual-token authentication for Nakama game servers with Amazon Cognito on AWS¶
Summary¶
An AWS Architecture Blog reference architecture for connecting two independent session systems — Amazon Cognito (managed identity, JWT-issuing) and Nakama (open-source game server with its own session tokens) — behind a default-closed multi-layer routing infrastructure. A server-side Go runtime hook validates the Cognito JWT and bridges the verified identity into a Nakama session token. The infrastructure enforces that the only path to the game server is Internet → CloudFront → WAF → ALB or NLB → ECS, with no hop skippable, using security-group chaining and explicit route allowlists. The WebSocket connection lifecycle is managed through a four-control model (ping/pong keepalive, pong timeout, session expiry at connect, single-socket enforcement) that works within the NLB's non-configurable 350-second TCP idle timeout.
Key takeaways¶
-
Dual-token pattern: Amazon Cognito owns player identity (short-lived JWT, 1-hour default), Nakama owns game sessions (session token, 2-hour expiry). Neither system depends on the other at runtime after the initial bridge — the Go hook validates the Cognito JWT exactly once and overwrites the Nakama user ID with the verified
subclaim. -
Default-closed ALB routing: The ALB default listener action returns
403 Forbidden. Only explicitly-listed paths (/healthcheck,/v2/account/authenticate/*,/v2/*,/v1/*) reach the game server. Any unlisted path is rejected at the load-balancer layer before reaching Nakama — limits the attack surface to the declared API. -
Dual load-balancer architecture (ALB + NLB): The ALB operates at Layer 7 for HTTP API traffic (path-based routing, explicit allowlist,
403defaults). The NLB operates at Layer 4 for WebSocket traffic (TCP passthrough, no HTTP inspection). CloudFront routes/ws*to the NLB and everything else to the ALB — each connection type gets appropriate handling behind a single HTTPS endpoint. -
Security-group chain: Only CloudFront's managed prefix list can reach the ALB and NLB. The ECS task security group allows inbound only from the ALB and NLB security groups. An additional application-layer check: CloudFront sends a shared secret in an
X-CloudFront-Secretheader; ALB listener rules reject requests missing the correct value. -
JWKS cache with thundering-herd protection: The Go hook caches Cognito's signing keys with a 1-hour TTL. A 30-second re-fetch guard (
time.Since(c.fetched) < 30*time.Second) prevents multiple goroutines from simultaneously hitting the JWKS endpoint when the cache expires — a mutex-guarded singleflight pattern for key rotation. -
WebSocket lifecycle under NLB TCP passthrough: The NLB drops idle TCP flows after 350 seconds (non-configurable AWS default). Four controls manage this:
- Ping interval: 10s — Nakama sends WebSocket ping every 10 seconds, keeping the flow active well within the idle timeout.
- Pong wait: 20s — if client doesn't respond, Nakama closes the connection.
- Token expiry: 7200s (2 hours) — Nakama validates session token from the
tokenquery parameter at WebSocket connect time, rejecting expired tokens before processing game messages. -
Single socket: true — a new connection from the same user kills the previous one, preventing split state across stale connections.
-
JWT validation order: Five checks in sequence — token format (three dot-separated parts), algorithm enforcement (RS256 only), RSA signature verification against JWKS, expiry check, and issuer + audience (client_id) matching. The hook never trusts the identity string sent by the client body — it discards it and overwrites with the verified
subclaim. -
SRP (Secure Remote Password) authentication: Cognito uses
USER_SRP_AUTHflow — the password never leaves the client device. No client secret is needed because the App Client is configured as a public client (generate_secret=false); security comes from the SRP protocol itself. -
Infrastructure as Code: Six Terraform modules (network, compute, auth, cdn, waf-cloudfront, ops) with a separate bootstrap module for S3 state backend + KMS key.
make deploybuilds/pushes the Nakama container to ECR with auto-incrementing tags then runsterraform apply. -
Generalisation: The four-layer WebSocket lifecycle model (keepalive, timeout, session expiry at connect, one-connection-per-user) applies to any real-time server behind an NLB TCP passthrough — Colyseus, Photon, custom WebSocket backends, or any server managing persistent connections. If the server lacks built-in ping/pong, application-level heartbeat messages serve the same role.
Operational numbers¶
| Metric | Value |
|---|---|
| NLB TCP idle timeout | 350 s (non-configurable) |
| Nakama ping interval | 10 s |
| Nakama pong wait | 20 s |
| Session token expiry | 7200 s (2 hours) |
| Cognito JWT default expiry | 3600 s (1 hour) |
| JWKS cache TTL | 1 hour |
| JWKS re-fetch guard | 30 s |
Caveats¶
- No production scale numbers: this is a reference architecture with demo Terraform, not a production retrospective. No TPS, latency distribution, or cost numbers disclosed.
- Token in query parameter: the session token travels as
?token=...in the WebSocket upgrade URL — appears in server access logs, load balancer logs, CloudFront logs, and browser history. Mitigations: TLS in transit, short-lived tokens, single-socket invalidation. The post recommends log-redaction policies. - Nakama-specific Go plugin constraint: Nakama's runtime only supports Go plugins
(
runtime.Initializerinterface). The JWT validation logic is portable to any OIDC- compliant server but the plugin mechanism is Nakama-locked.
Source¶
- Original: https://aws.amazon.com/blogs/architecture/dual-token-authentication-for-nakama-game-servers-with-amazon-cognito-on-aws/
- Raw markdown:
raw/aws/2026-06-29-dual-token-authentication-for-nakama-game-servers-with-amazo-3b190416.md
Related¶
- systems/amazon-cognito — managed identity / JWT issuer
- systems/amazon-cloudfront — single HTTPS entry point
- systems/aws-waf — edge traffic filtering
- systems/aws-alb — Layer 7 HTTP routing with default-deny
- systems/aws-nlb — Layer 4 TCP passthrough for WebSocket
- systems/amazon-ecs — Nakama container hosting on Fargate
- concepts/thundering-herd — JWKS cache prevents thundering herd on key rotation
- concepts/dual-token-authentication — the central concept
- patterns/dual-token-session-bridge — the central pattern
- patterns/default-closed-alb-routing — explicit allowlist at LB layer
- patterns/ping-pong-keepalive-under-nlb — WebSocket lifecycle management