JWT vs Session Cookies

A JWT is a token format; a cookie is a browser storage and transport mechanism. They are not mutually exclusive. A cookie can carry an opaque server-session identifier or a JWT. Separately, a system can validate a token locally or consult server-side state.

For a conventional first-party web application, evaluate the framework's maintained server-session design with an appropriately protected cookie first. Choose independently validated tokens when the actual API and trust boundaries justify them, and define revocation and storage behavior explicitly. This is a starting point for design review, not a universal prescription.

TL;DR

Quick Example

This complete Python model illustrates a revocation tradeoff with fictional time values. It does not parse JWTs, validate signatures, authenticate a user, implement cookies, or authorize a resource. Its inputs represent already trusted validation results and server policy.

Without consulting changed state, the modeled validator still accepts the unexpired credential. A session lookup can observe revocation, subject to the real store and cache behavior. Either an opaque identifier or a JWT can participate in a state-checked design; the token's shape does not dictate the lookup policy.

Save this as revocation_model.py and run python revocation_model.py. The point is to make the revocation requirement visible before selecting an implementation.

Core Concepts

Format, transport, and validation state are independent

An opaque session identifier references server-side information. A JWT carries claims in a defined format and can receive cryptographic protection. A cookie tells the browser when and how to attach stored data to requests. An authorization header is another transport choice.

RFC 8725 addresses JWT validation, including algorithm selection and issuer/audience conditions. Decoding a payload is not validation. Use a maintained library and a fixed application policy for accepted tokens; do not trust a received header to choose arbitrary keys or algorithms.

Browser storage creates different exposure paths

A cookie marked HttpOnly is unavailable to ordinary page-script reads through document.cookie. A JavaScript-readable token store exposes the token to malicious script running with the application's privileges. Neither choice makes XSS harmless: malicious code can still act through an active application context.

OWASP's session-management guidance explains cookie properties, identifier renewal, expiration, and session lifecycle. Treat these as a connected design rather than a checklist of magic flags.

Sending credentials automatically affects CSRF defenses

Browsers attach eligible cookies according to cookie rules. Review the application's state-changing requests and cross-site interaction needs, including a supported CSRF defense. SameSite is useful defense in depth, not a complete replacement for the application's anti-CSRF design.

Moving a token to an explicit header changes the browser's automatic-attachment behavior, but introduces storage and client-code decisions. CORS settings do not substitute for authorization, and a token format alone does not decide whether a CSRF attack is possible.

Compare complete session designs

These columns describe example designs, not immutable properties of cookies and JWTs. A JWT in a cookie with a server-side session check crosses the columns. A useful architecture diagram labels the actual mechanism at each step.

Decide from revocation and trust boundaries

First ask how quickly a lost device, disabled account, or changed role must lose access. Define the allowed delay and test it. “The token expires eventually” is not enough when the product requires a faster response.

Then identify who validates the credential. One first-party application may benefit from a simple maintained session system. Several independent APIs may justify an identity provider and tokens with explicit intended recipients. Keep browser-facing credentials distinct from service-to-service credentials when their audiences and privileges differ.

Next, design refresh and logout. Clearing one browser value does not necessarily invalidate all previously issued credentials. Define whether logout affects one device, one session family, or all sessions, and which checks enforce that result. Consider stale caches and concurrent requests in the acceptance criteria.

Finally, review recovery and failure. Decide what happens when the session store is unavailable, keys rotate, refresh fails, or a client resumes after a long absence. Prefer an understandable failure over silently bypassing validation. Add observability without recording raw credentials.

Best Practices

Use the framework's supported lifecycle

Rely on maintained session or identity libraries for identifiers, renewal, token verification, and cookie construction. Verify the real deployment's HTTPS, proxy, host, and path assumptions rather than copying flags into an unrelated environment.

Keep authorization separate

An accepted session establishes an identity context, not permission to every object. Enforce the required tenant, resource, and action checks with sufficiently fresh policy information.

Test the event that should remove access

Revoke a test session, change a permission, and exercise old credentials in a disposable environment. Check all relevant APIs and browser states, including refresh and cached validation paths.

Common Mistakes

Treating JWT and cookie as opposite choices

Bad: Assume choosing JWT forbids cookies or that using cookies requires one particular token format.

Correct: Document format, transport, storage, and validation state as separate decisions.

Calling signed claims confidential

Bad: Put secrets into a signed token because its payload looks encoded.

Correct: Treat ordinary signed-token claims as readable by holders. Minimize claims and use the appropriate protection and transport for confidential data.

Calling logout revocation without testing it

Bad: Delete the browser's current value while an old copied credential remains accepted elsewhere.

Correct: Define the revocation promise and verify the server's enforcement and propagation delay.

FAQ

Are JWTs stateless?

They can be validated without a per-token lookup in some designs, but systems may still need revocation, refresh, key, and authorization state. JWT does not require the absence of server state.

Does HttpOnly prevent XSS?

It restricts script access to the cookie value. It does not prevent malicious script from running or acting through the application. Keep XSS prevention and session controls together.

Are sessions easier to revoke?

A design that checks mutable session state can observe its invalidation. Actual delay depends on caches, consistency, and which services perform the check. Test the promised behavior.

Should every browser app use a token in local storage?

No. Choose from the application's trust, transport, storage, and lifecycle requirements. Evaluate supported first-party session designs before adopting a JavaScript-readable bearer-token store by default.

Related Topics

References