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
- Separate three choices: credential format, how the browser sends it, and what state the server checks.
- JWTs can be signed and/or encrypted; a commonly used signed JWT does not conceal its claims.
- Local validation can reduce some lookups but does not make revocation, key distribution, or authorization disappear.
- Cookie flags protect specific properties; they do not make cross-site scripting or cross-site request forgery irrelevant.
- Server sessions need secure identifiers, lifecycle controls, a dependable state store, and a scaling plan.
- Use supported authentication libraries and protocols rather than building a production session system from a comparison example.
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
- JSON Web Tokens — Token structure and validation policy.
- Authentication — Trusted identity and account lifecycle.
- Authentication vs Authorization — Identity and resource permissions.
- Hashing vs Encryption vs Encoding — Representation and protection are separate choices.