This article details a dual-token authentication system for game servers using Amazon Cognito for player identity and Nakama for game sessions. It outlines an architecture that integrates these systems with distinct token lifecycles, ensuring a seamless player experience. The solution employs AWS services like CloudFront, WAF, ALB, NLB, and ECS Fargate to build a secure, scalable, and highly available routing and authentication layer for real-time game applications.
Read original on AWS Architecture BlogThe core problem addressed is combining a managed identity provider (Amazon Cognito) with a game server's native session system (Nakama) without disrupting gameplay. The proposed solution uses a dual-token approach: Cognito issues a JWT for player identity, which is then exchanged for a Nakama session token via a server-side Go hook. This decouples identity management from game sessions, allowing independent validation and lifecycle management for each token type.
The architecture features a default-closed routing layer, with Amazon CloudFront as the single HTTPS entry point, providing edge caching and security via AWS WAF. HTTP API traffic is routed through an Application Load Balancer (ALB) to Nakama instances on Amazon ECS Fargate, while WebSocket traffic is handled by a Network Load Balancer (NLB) using TCP passthrough. This separation allows specialized handling for different traffic types.
Load Balancer Specialization
The use of both an ALB (Layer 7) and an NLB (Layer 4) is crucial. The ALB provides advanced HTTP features like path-based routing, listener rules, and HTTP-level security, returning `403` for unlisted routes. The NLB, conversely, performs raw TCP passthrough, which is essential for efficient and low-latency WebSocket connections, allowing Nakama to directly manage the connection lifecycle. CloudFront intelligently routes traffic based on URL patterns (`/ws*` to NLB, others to ALB).
The system ensures security by never trusting client-provided identity directly. A server-side Go hook within Nakama validates the Cognito JWT. This involves checking the token format, algorithm (RS256), signature against a cached JWKS, expiry, and issuer/audience claims. Critically, the hook discards any client-sent user ID and overwrites the Nakama user ID with the cryptographically verified `sub` claim from the JWT, preventing impersonation. The JWKS cache implements a thundering herd protection mechanism to efficiently handle key rotation.
func validateCognitoJWT(token string, env map[string]string) (string, error) {
// ... token parsing, header extraction
if header.Alg != "RS256" { return "", runtime.NewError("unsupported algorithm", 3) }
// ... fetch public key from JWKS cache
if err := rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, hash[:], signatureBytes); err != nil { return "", runtime.NewError("invalid token signature", 16) }
// ... validate claims: expiry, issuer, audience
return claims.Sub, nil // sub claim becomes the Nakama user ID
}