Software Architecture

Authentication & Session Design — Server Session vs JWT

Authentication & Session Design — Server Session vs JWT

About this article

This article is the Software Architecture category’s final installment (7th) in the Architecture Crash Course for the Generative-AI Era series, covering server-side authentication and session.

The question: “how do we hold the session post-login?” Server session vs JWT, OAuth 2.0/OIDC, safe Cookie attributes — landing on the practical axis: “same-domain = Cookie, cross-domain = JWT.”

CoveredNot covered (-> other articles)
Server session, JWT, OAuth, server-side Cookie configAuth strength (MFA / Passkey / IDaaS), authorization (IAM), browser defense (XSS / CSP)

What is authentication and session design in the first place

Authentication and session design is, in a nutshell, “deciding the mechanism for remembering logged-in users.”

Imagine entering an amusement park. You show your ticket at the gate for identity verification (authentication) just once, then inside the park you just show your wristband (session) to ride attractions. Whether the wristband is managed server-side or carried by the visitor — this choice is the difference between server-session and JWT approaches.

Why authentication and session design matters

Session-management mistakes create fatal security holes

If you store JWT in the wrong place, XSS steals it; if you forget Cookie attribute settings, CSRF exploits it — session design mistakes directly lead to unauthorized access.

Microservices multiply authentication complexity

With microservices, every service must verify the user independently. Deciding “who verifies, and how is session state shared” at design time is essential; bolting it on later touches every endpoint.

Authentication vs authorization

Implementing login often confuses authentication (AuthN) and authorization (AuthZ). They serve completely different purposes and must be separated at design time.

Authentication confirms “who you are” — verifying identity via password or Passkey. Authorization decides “what you can do” — determining whether logged-in users can “see the admin panel” or “edit this data”, etc.

TermSubstanceEnglish (abbreviation)
AuthenticationConfirm who you areAuthentication (AuthN)
AuthorizationDecide what you can doAuthorization (AuthZ)

“Logged in, so they can do admin operations” is the classic confusion. Design AuthN and AuthZ as separate things.

The two main session-management approaches

The mechanism letting users continue using APIs without re-login post-authentication is session management. It splits broadly into “keeping state on the server” and “keeping state on the client.”

ApproachState locationTrait
Server session (Cookie)Server (Redis, etc.)Immediate revocation, mature track record
JWT (token)ClientStateless, easy to scale

Neither is superior; the default split: “same-domain ordinary web app = server session”, microservice-crossing or external-integration APIs = JWT.

Server-session approach

Server session: on successful login, the server issues a random session ID and stores it in the client’s Cookie. Subsequent requests carry the Cookie; the server looks up Redis or similar to retrieve user info from the session ID.

The traditional approach with strengths of immediate revocation (delete from Redis to invalidate), localizing leak impact, and mature libraries. Downsides: a separate session store to operate, and on horizontal scaling all servers need to share the store.

StrengthsWeaknesses
Immediate revocation (logout is simple)Session store required
Mature track record and librariesStateful (server-side state)
Easier to localize leak damageSharing required at scale

For same-domain general web apps and SPAs, server session is sufficient and safe.

JWT (JSON Web Token) approach

JWT packages user info itself into a signed token handed to the client. Servers authenticate by verifying the signature each time, so they hold no state — stateless is the headline trait.

In multi-microservice configurations, each service can authenticate independently. Watchpoints: immediate revocation is hard (token valid until expiry), private-key leak enables impersonation of all users, payload is base64-decodable by anyone.

Header.Payload.Signature
─────  ────────  ─────────
algo  user info  tamper-detection
                 signature

JWT pitfalls

When adopting JWT, understand the pitfalls. The “difficulty of revocation” is JWT’s fundamental nature; workarounds need separate mechanisms.

  • Cannot be revoked: JWT is valid until expiry, so leaks can’t be invalidated immediately. Solution: “short-lived Access Token + Refresh Token in httpOnly Cookie” is the default.
  • Private-key leak is fatal: a leaked signing secret lets attackers forge tokens for any user.
  • localStorage storage is XSS-vulnerable: XSS (Cross-Site Scripting — embedding malicious scripts) easily steals tokens. Save in httpOnly Cookies for safety.
  • Don’t put secrets in the payload: base64 isn’t encryption. Anyone can decode it.

“Access Token (short-lived) + Refresh Token (long-lived, httpOnly)” is the modern JWT operational default.

OAuth 2.0 / OIDC

OAuth 2.0 is the protocol for “delegating authorization.” Widely used as a mechanism for handing API-access permissions to third-party apps, like “give this app read access to Google Drive.”

OpenID Connect (OIDC) is an extension standardizing authentication on top of OAuth — features like “Sign in with Google” are realized via OIDC.

These are standardized protocols, so the modern mainstream is delegating to authentication services (Auth0 / Firebase Auth / Cognito / Okta) rather than implementing in-house. In-house implementation has high vulnerability risk; including MFA and Passkey support, the operational load is large.

ProtocolRole
OAuth 2.0Delegate API-access permissions to third-party apps
OpenID Connect (OIDC)OAuth-based extension for authentication

Examples: Google / Apple / GitHub login, internal SSO (Okta, Auth0).

For authentication, delegation to external services is the standard. Avoid in-house implementation for safety.

Major OAuth flows

OAuth has multiple flows by use. For web and mobile apps, “Authorization Code + PKCE” is the de facto standard; new adoption of other flows is rare.

The decision axis: “who protects what.” The Authorization Code flow receives the authorization code via the browser and exchanges it for tokens server-side — a two-step mechanism. Tokens themselves don’t end up in browser history or URLs, lowering leak risk.

Combining with PKCE means even if the authorization code is intercepted mid-flight, attackers can’t exchange it for tokens. This makes it safe for SPAs and mobile — “clients that can’t hold secrets.”

sequenceDiagram
    participant U as User
    participant C as Client<br/>(SPA/mobile)
    participant A as Auth server
    participant R as Resource server<br/>(API)
    U->>C: Login
    C->>C: Generate code_verifier<br/>code_challenge = SHA256
    C->>A: Auth request<br/>+ code_challenge
    A->>U: Login screen
    U->>A: Credentials
    A->>C: Auth code (short-lived)
    C->>A: Token exchange<br/>code + code_verifier
    A->>A: Verify SHA256(code_verifier)<br/>matches code_challenge
    A->>C: Access Token + Refresh Token
    C->>R: API call<br/>+ Access Token
    R->>C: API response
FlowUse
Authorization Code + PKCE (Proof Key for Code Exchange — code-interception protection)SPA / mobile / web app (standard)
Client CredentialsServer-to-server (no user)
Device CodeTVs, CLIs — input-difficult devices
Implicit FlowDeprecated (security issues)
Password GrantDeprecated (passing password to a third party by design)

For new builds, PKCE-paired Authorization Code only. Don’t use Implicit / Password Grant from old sources.

Selection by case

Traditional web app (same domain)

Server session (Cookie). The most mature approach; logout is also simple. Default to this without a special reason.

SPA + API (same domain)

Server session (Cookie). SPA = JWT” is a misconception. httpOnly Cookies are sufficiently safe.

Microservices / cross-service authentication

JWT + Refresh Token. Stateless property raises inter-service independence.

External login federation (Google, GitHub, etc.)

OpenID Connect (OIDC). Delegate to Auth0 or Firebase Auth — practical.

Internal corporate systems (SSO required)

OIDC + SSO platform (Okta / Auth0 / Entra ID). Permission changes from HR moves can be unified.

Whether using server session or putting JWT in Cookies, Cookie-attribute settings drive safety. These prevent the majority of security incidents.

AttributeRole
HttpOnlyNot readable from JavaScript (XSS protection)
SecureSent only over HTTPS
SameSite=Lax or StrictPrevent CSRF (Cross-Site Request Forgery — fake requests exploiting login state)
Domain / PathLimit send scope to minimum
Max-Age / ExpiresMake expiry explicit

“HttpOnly + Secure + SameSite=Lax” is the minimum line for web apps. Skipping is unacceptable.

Implementation in Express / Hono / Next.js typically looks like:

// Recommended: pass via Cookie (XSS-protected)
res.cookie("session", token, {
  httpOnly: true,           // not readable from JS (XSS protection)
  secure: true,             // HTTPS only
  sameSite: "lax",          // CSRF protection
  maxAge: 60 * 60 * 1000,   // 1 hour
  path: "/",
});

// Not recommended: localStorage (one XSS = leak)
localStorage.setItem("token", token);

When using JWT, store in HttpOnly Cookies rather than localStorage. Refresh Tokens go in a separate Cookie or server-side KV store. Since 2024 security guidelines, “JWT + localStorage” is effectively forbidden.

Session / token expiry numeric gates

Note: industry rates as of April 2026. Periodic refresh required.

Session design decided “casually” always produces vulnerabilities; set specific numerical baselines at the start. Industry defaults:

SettingRecommendedReason
Access Token (JWT) expiry15 minutesMinimize leak damage
Refresh Token (httpOnly Cookie) expiry14-30 daysBalance with user convenience
Session Cookie expiry30 minutes - 24 hours (depends on business)By business form
Session ID entropy128+ bitsUnguessable length
JWT signing algorithmRS256 / EdDSAHS256 is symmetric; key distribution care
JWT none algorithmAbsolutely forbidden2018 library vulnerabilities widespread
Refresh Token RotationEnableTheft detection invalidates all sessions
CSRF tokenSameSite=Lax + CSRF tokenDefense in depth

JWT algorithm: use RS256 (RSA signing) or EdDSA, and never allow the none algorithm. 2018 saw multiple JWT-library vulnerabilities permitting none.

Refresh Tokens issue new values on each use (Rotation); reuse of old values invalidates the entire session — modern default.

Session / token operational traps

Common session-implementation failure patterns. All directly enable account takeover and impersonation.

Forbidden moveWhy
JWT in localStorageEasily stolen via XSS. httpOnly Cookie is the rule
Cookie without HttpOnly / Secure / SameSiteXSS, eavesdropping, CSRF all succeed
”SPA = JWT” misconception, avoiding Cookie sessionsSame-domain Cookie session is safer and simpler
JWT without revocation designLeaks can’t be invalidated. Short-lived Access + httpOnly Refresh is the default
Allowing JWT none algorithmForgery without signature verification possible. 2018 library vulnerabilities widespread
Adopting OAuth Implicit Flow / Password Grant for new buildsDeprecated since 2020. Authorization Code + PKCE is standard
No Refresh Token RotationTheft cannot be detected. Reissuing on each use is modern default
Weak Session ID entropyGuessing-based takeover. 128+ bit randomness required
Default Cookie attributesWithout explicit settings, neither Secure nor HttpOnly applies. Always set explicitly
Logout only deletes CookieServer-side Refresh Token survives. Revocation API required
Giving up on JWT revocation and not implementing RotationShort-lived Access Token + Refresh Token Rotation effectively enables immediate revocation; it’s a design issue, not JWT’s fate
Treating authentication and authorization as the same design”Logged in = full permissions” is textbook confusion; AuthN and AuthZ require separate design

Authentication-method-specific traps (password storage, MFA, Passkey) are in the “Security Architecture” category. This article is limited to session-tech traps.

AI decision axes

AI-era favorableAI-era unfavorable
OAuth / OIDC standard-flow implementationCustom token design
Short-lived Access + httpOnly RefreshLong-lived, localStorage
Cookie attributes explicit in code (HttpOnly, etc.)Default-reliance, implicit settings
SDK calls (Auth.js, Clerk SDK)DIY session management
  1. Decide approach by domain structure — same-domain Cookie, cross-domain JWT.
  2. Make Cookie attributes explicit — HttpOnly + Secure + SameSite=Lax is the minimum line.
  3. JWT: short-lived Access + Refresh Token Rotation — standard answer to non-revocability.
  4. OAuth: Authorization Code + PKCE only — don’t pick Implicit / Password Grant.

”JWT is new and cool” illusion (industry case)

A misconception spread during the late-2010s SPA boom: “You’re doing SPA, so JWT in localStorage is modern.” Even for ordinary same-domain web apps, jumping to JWT and avoiding Cookie sessions kept happening.

There are stories of building JWT + localStorage configurations around 2017 with the same assumption, only to be told by a junior reviewer “are you allowed to keep this in localStorage?” during XSS review.

The result: a single XSS leaks all users’ JWTs, and JWT’s nature means no immediate revocation — attackers run wild until expiry. Slack’s 2022 incident of stolen employee tokens via GitHub used standard token authentication; the code worked correctly. Even so, storage location and revocation operations were the weak points exploited — a classic example.

JWT shines for “places where stateless is required” — microservice-crossing, external integration. For ordinary same-domain web apps, it’s overkill. If a server session (Cookie) suffices, that’s safer and simpler. Postponing this judgment leads to a rewrite later.

“Newer = correct” doesn’t apply to authentication. Mature methods continue being chosen for reasons.

What you must decide — what’s your project’s answer?

Articulate your project’s answer in 1-2 sentences for each:

  • Session approach (server session / JWT / hybrid)
  • Cookie attributes (HttpOnly / Secure / SameSite)
  • Access Token expiry / Refresh Token Rotation
  • JWT signing algorithm (RS256 / EdDSA, none forbidden)
  • OAuth flow (Authorization Code + PKCE essentially only)
  • Logout processing (revocation API implementation)
  • Session ID entropy (128+ bits)

Authentication methods (MFA, Passkey, IDaaS selection, password policy) are decision items for the auth-design article in the “Security Architecture” category.

Summary

This article covered authentication and session design — server session vs JWT, OAuth/OIDC, Cookie attributes.

Same-domain = Cookie, cross-domain = JWT, external integration = OIDC. Cookie attributes explicit, JWT short-lived + Refresh Token Rotation, OAuth = PKCE only. The realistic answer for session operation at the software-architecture level.

This concludes the “Software Architecture” category’s 8 articles. The next category is “Application Architecture” — class design, domain logic, naming conventions, error handling.

Back to series TOC -> ‘Architecture Crash Course for the Generative-AI Era’: How to Read This Book

I hope you’ll read the next article as well.