Authentication Implementation: JWT vs Session Cookie

JWT (Bearer tokens) and Session Cookies are two mainstream approaches to authentication. This article explains JWT access/refresh token flows and cookie-backed server sessions (with a session store like Redis), then compares scalability, immediate logout/revocation behavior, and security considerations (XSS, CSRF) to help you choose the right approach for interviews and real projects.

CChia1104
Posts7 minutes read

During my job interviews this year, I was frequently asked one question: "Have you implemented login authentication?" Since I was applying for frontend engineer positions but had backend experience, I typically discussed two approaches: JWT (Bearer token) and Session Cookie. A quick clarification: when I mention "Session" here, I'm referring to backend Sessions used with frontend Cookies—not the browser's one-time session cookies. Since these work together, I often discuss them as a unit, though I was surprised to find that some interviewers weren't familiar with the Session Cookie approach.

JWT(JSON Web Token)- Bearer token

JWT has become nearly standard for authentication in modern frontend-backend separation architectures, especially when the frontend is built as a Single Page Application (SPA).

Its core concept is stateless the backend doesn't need to store user login state, the server simply validates the token's validity.

JWT Typically Contains Three Parts

  1. Header: Specifies the signing method and data format
  2. Payload: Contains user information and permission data
  3. Signature: Verifies that the data hasn't been tampered with

This design makes JWT well-suited for distributed systems or microservice architectures, since there's no need to share session state, reducing server burden.

However, the tradeoff is that once a token is issued, it's difficult to revoke mid-lifecycle unless it expires or is manually blacklisted—a risk point many beginners overlook.

Common Issues When Using JWT

  • Token Size: JWTs are usually placed in headers and sent with every request; oversized tokens can drag down performance
  • Cannot Logout Immediately: JWTs cannot be actively expired unless you implement a blacklist

Bearer token

When discussing JWT, the most common implementation is sending it via the Authorization header using the Bearer token format.

A typical request looks like this:

GET /me HTTP/1.1
Host: api.example.com
Authorization: Bearer <ACCESS_TOKEN>

This access_token can typically be stored directly in localStorage, and the frontend retrieves it to place in the header. To refresh the token, use a refresh_token.

Some argue that storing tokens in cookies with HttpOnly is safer to prevent XSS (Cross-Site Scripting). However, I believe XSS can be prevented directly using CSP (Content Security Policy). After all, Bearer <ACCESS_TOKEN> needs to be manually placed in headers by the frontend—if using HttpOnly, the frontend cannot access it anyway.

Flow

In contrast, the traditional Session Cookie mechanism is stateful after login, the server generates a Session ID and returns it to the browser as a cookie.

With each request, the browser automatically attaches this cookie, and the backend matches it against stored session data.

Since state is controlled by the backend, operations like logout, permission changes, and forced logoff are easier to implement. This mechanism remains very stable in single-server architectures or when using Redis for session storage, and it's convenient for managing security (such as setting HttpOnly or Secure flags).

  • Pros: Simple, passively secure (server-controlled), easy to manage login state
  • Cons: Horizontal scaling requires shared sessions (needs external storage like Redis), not suitable for highly distributed architectures​

Session Management

In Session Cookie mode, "refresh" actually means extending the session's validity time on the backend (sliding expiration), rather than issuing a new token like JWT does.

The typical flow works as follows:

  • At Login: The backend creates a session record (stored in memory or Redis), generates a sessionId, and sets an expiration time (e.g., 30 minutes). It returns Set-Cookie: sessionId=random_string; HttpOnly; Secure; SameSite=Strict to the browser.

  • With Each Request: The browser automatically sends the sessionId cookie. The backend uses this sessionId to query the session store (e.g., Redis). If data exists and hasn't expired, the user is considered logged in. If "sliding expiration" is enabled: whenever a request is validated, the session's expiration time is extended (e.g., another 30 minutes).

  • At Logout or Forced Logoff: The backend directly deletes the session data corresponding to that sessionId. Even if the browser still has the old cookie, it cannot find the session and is treated as logged out.

Flow

Comparison

AspectJWT (Bearer Token)Session Cookie
State StorageStateless, login info in token itself, stored by clientStateful, login info stored in server's session store, client only stores sessionId
ScalabilitySuitable for multi-service, cross-domain, microservice distributed architectures—no need for shared session storeSingle server or shared sessions via Redis/database works well for typical backend or enterprise internal systems
Revocation & LogoutDifficult to revoke immediately in pure stateless implementation; requires blacklist or token versioning mechanismsServer can delete or mark session immediately; forced logoff and permission updates take effect instantly
Security ConcernsIf token is stored where JS can access it, XSS attacks can directly steal it; larger size may leak excessive informationCookie is just a random ID; can be enhanced with HttpOnly, Secure, SameSite attributes for stronger XSS and CSRF protection
Use CasesSPAs, mobile apps, multi-service scenarios, or APIs exposed externally—when you have the capability to design complete token lifecycle and security strategiesTraditional web, backend management systems, enterprise internal systems, or when stable, easily controllable login state is needed

In the real world, JWT and Session Cookie are rarely a simple "either-or" choice. More often, they're mixed and integrated into larger architectures: for example, web interfaces use Session Cookies to manage login state, while API Gateways or internal microservices use JWT to exchange authorization information.

When a project is small with only a single backend and simple deployment—Session Cookie is often the more stable choice and easier for teams to understand and maintain. Conversely, when systems begin splitting into services, implementing frontend-backend separation, or even opening APIs to third parties, JWT's "self-describing, verifiable across multiple services" characteristics become extremely valuable.

Written by: Chia1104 CC BY-NC-SA 4.0

Chia1104
©
Chia1104