Authentication
The browser client authenticates users through AWS Cognito via aws-amplify v6. Sign-in uses the Cognito hosted UI (OAuth authorization code flow with federated identity providers).
Configuration
Amplify is configured once at app startup in amplifyAuth.js, called from index.js:
- User pool ID and app client ID from config
- Cookie storage (7-day expiry, secure, domain from
COGNITO_COOKIE_DOMAIN) - OAuth domain, scopes (
openid,email,aws.cognito.signin.user.admin),responseType: "code"
Redirect URLs come from COGNITO_REDIRECT_LOGIN and COGNITO_REDIRECT_LOGOUT. See Configuration.
polyfills.js imports aws-amplify/auth/enable-oauth-listener so OAuth callbacks complete in production Vite builds (the listener must live in the root bundle, not a lazy chunk).
Sign-in flow
- User clicks login →
signInWithRedirect()viaredirectToSignIn()(navbarProfileMenu.jswhen logged out). - Browser redirects to Cognito hosted UI.
- On success, Cognito redirects back with an authorization code; Amplify exchanges it for tokens.
resolveAuthSession()stores the JWT and normalized identity fields (userId,username,email) in ZustandauthSession; UI components read them viauseAuthSession().
Session bootstrap
Skeleton calls resolveAuthSession() on load:
- Success → marks
localStorage.wasLoggedIn = "1". - Failure with prior
wasLoggedIn, no intentional logout, and a non-anonymous route → session expired; auto-triggerssignInWithRedirect().
Token refresh
getAuthTokenFromSession() returns a cached JWT while it is still valid (with a 5-minute buffer before expiry). When the token is stale or missing, it silently calls fetchAuthSession() again so Amplify can refresh the ID token without flashing the login UI.
callAuthApi retries once with a forced session refresh on HTTP 401/403 before redirecting to Cognito.
Sign-out
ProfileMenu.js and UserSettingsModal.js set sessionStorage.intentionalLogout = "1" before signOut() so the expiry handler does not immediately re-login.
User profile (globalMe)
After login, useProfileBootstrap calls fetchProfile() (me_profile), which populates Zustand globalMe (bots, settings, activeGames, etc.). NotificationBell.js calls fetchNotifications() (list_notifications) once for the navbar feed. Me.js on / calls fetchDashboard() (me_dashboard) for games and challenges and fetchNotifications() in parallel as a gap filler; WebSocket notification messages trigger a debounced fetchNotifications() while connected (see WebSockets).
New user onboarding
If the backend returns an incomplete profile, NewProfile.js prompts the user to set a display name and preferences.
Token usage
- Auth API calls —
Authorization: Bearer <jwt>viacallAuthApi - WebSocket — token sent on subscribe (see WebSockets)
- Push notifications — token sent with
save_push(see Notifications)
Profile updates
Email changes go through Amplify v6 attribute APIs (updateUserAttribute, confirmUserAttribute) in UserSettingsModal, not the node-backend authQuery layer.
Avatar customization (DiceBear style + seed) is saved via update_user_settings as settings.all.profile.avatar and mirrored on the public user list for player profiles. See User Settings → Avatar in UserSettingsModal.js and UserAvatar.js.
Cognito setup (ops)
Pool configuration, callback URLs, and identity providers are documented in Backend deployment. Dev and prod use separate pools; tokens are not interchangeable across stages.