Frontend Authentication: Cookies, Tokens, and Session Management

Authentication is one of the most error-prone parts of a frontend application. Get it wrong and you have account takeover, session hijacking, or worse. Senior frontend interviews probe whether you understand the modern patterns: cookies vs tokens, OAuth flows, refresh strategies, and the security implications of each choice.

The two main patterns

Session cookies

Server creates a session, stores in DB. Client gets a session ID in a cookie. Server validates the cookie on every request.

Pros:

  • HttpOnly cookies are not readable from JS — XSS-safe for the token
  • Centralized session control on server
  • Easy logout (server invalidates session)

Cons:

  • Requires same-origin or careful CORS setup
  • Stateful server (database lookup per request) — though usually cached

JWT tokens

Server signs a JWT with claims about the user. Client sends it in Authorization header.

Pros:

  • Stateless — no server-side session lookup
  • Works across domains and microservices
  • Standards-based

Cons:

  • Cannot easily revoke (until expiry)
  • Token storage on client is a security headache
  • Larger payload than session cookies

Where to store tokens

This is the most-debated topic. The options:

  • localStorage: JS-readable. XSS can steal tokens. Wrong choice.
  • sessionStorage: same XSS risk; cleared on tab close.
  • HttpOnly cookies: not JS-readable. XSS cannot steal directly. Best for tokens.
  • In-memory: JS variable. Safe from XSS but lost on reload — paired with refresh tokens.

The refresh token pattern

Modern recommended pattern:

  • Short-lived access token (5–15 minutes), in memory
  • Long-lived refresh token (1–30 days) in HttpOnly Secure SameSite=Strict cookie
  • Access token expires → use refresh token to get new access token
  • Refresh token compromised → server can revoke

This bounds the damage of token theft and gives the server centralized control.

OAuth and OpenID Connect

For “Sign in with Google/Apple/GitHub” flows:

  1. Frontend redirects user to provider with client_id
  2. User authenticates with provider
  3. Provider redirects back with authorization code
  4. Frontend sends code to backend
  5. Backend exchanges code for access + ID tokens directly with provider
  6. Backend validates ID token, creates own session for the user

Critical: never expose client_secret in frontend. Token exchange must happen server-side.

PKCE (Proof Key for Code Exchange)

Modern OAuth flows use PKCE to prevent authorization code interception. The flow:

  1. Frontend generates a random code_verifier; hashes to code_challenge
  2. Sends code_challenge with the auth request
  3. Provider returns code; frontend sends code + code_verifier
  4. Provider validates: code_verifier hashed must equal code_challenge

Used in SPAs and mobile apps. Most OAuth libraries handle PKCE automatically.

Handling auth state in React

Common pattern:

  • useAuth() hook exposes current user and login/logout
  • Provider at app root subscribes to auth state changes
  • ProtectedRoute redirects unauthenticated users
  • useEffect on app mount checks for existing session (e.g., calls /me endpoint)

Logout

For session cookies: hit a logout endpoint; server clears the session and Set-Cookie expires the cookie.

For JWTs: clear in-memory token and refresh token. Optionally call a revocation endpoint to invalidate the refresh token server-side.

Multi-device: server-side revocation lists allow logout-from-all-devices.

Multi-factor authentication

Common UX:

  1. User enters username + password
  2. Server returns “MFA required” with a temporary token
  3. User enters TOTP code or approves push
  4. Server validates; returns access token

WebAuthn (passkeys) is becoming standard. Modern auth allows skipping passwords entirely with passkey + device verification.

Common mistakes

  • Storing JWTs in localStorage
  • Logging tokens (in error reports, console)
  • Sending tokens via URL query parameters (logged in server access logs)
  • Not using HTTPS (cleartext credentials)
  • Not validating tokens server-side (trust)
  • Long-lived access tokens (no compromise window mitigation)

Frequently Asked Questions

Should I roll my own auth or use a service?

Use a service. Auth0, Clerk, Supabase Auth, AWS Cognito, Firebase Auth — all are battle-tested. Rolling your own is risky; subtle bugs are catastrophic.

Should I use sessions or JWTs?

For monolithic web apps: sessions (HttpOnly cookies). For multi-service or mobile apps: JWTs with refresh tokens. Both are valid; sessions are simpler.

What about passkeys?

WebAuthn / passkeys are the future. They eliminate passwords. Adoption is growing — Apple, Google, Microsoft all support. Add as an option alongside passwords for now.

Scroll to Top