Security basics every engineer needs path · Identity · Topic 5 of 16

Sessions vs tokens, and JWT pitfalls

Not started

Once a user has logged in, the client needs a way to prove it on every later request. There are two broad designs. A **server-side session** gives the client an opaque random ID and keeps the state on the server. A **self-contained token**, usually a JSON Web Token (JWT), puts signed claims in the client's hands so servers can check them without a lookup. Each has its place; the trouble usually comes from using JWTs where a session would have been simpler.

Why it matters: After login, every request is authenticated by whatever the client sends back. Get that wrong and an attacker doesn't need the password at all.

  1. 1Server-side sessions

    The client holds a long random ID in a cookie; the server keeps the session data. Easy to revoke: delete the row.

  2. 2Self-contained tokens

    A JWT carries claims signed by the issuer, so any service with the key can check it without a lookup. Hard to revoke before it expires.

  3. 3Signed is not secret

    A JWT's header and payload are just base64url-encoded JSON. Anyone holding the token can read them, so never put secrets in a JWT.

  4. 4Verify strictly

    Pin the expected algorithm, check signature, issuer, audience and expiry, and reject `alg: none`.

1

How it works

The explanation, with examples you can run.

Server-side sessions

The server creates a long random ID (at least 128 bits from a secure random generator), stores the session data against it, and sends it as a cookie with HttpOnly, Secure and SameSite. Each request looks the session up. Logging out, or kicking a stolen session, is deleting it. Issue a new ID at login and on privilege changes, so an ID planted before login (session fixation) is worthless afterwards, and expire sessions after idle time and an absolute maximum.

For a classic web app with one backend, this is usually the best choice.

JWTs

A JWT is three base64url-encoded parts joined by dots: header, payload (the claims), signature. Any service holding the issuer's key can verify it locally, which is useful across many services or between organisations. That is also its main weakness: a valid token stays valid until exp, unless every verifier also checks a revocation list.

It is easy to forget that the payload is readable by anyone:

⌘/Ctrl + Enter
OutputNot run yet

Press Run to see what your code prints, in order.

Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.

JWT pitfalls (RFC 8725)

  • Trusting the header's alg. Pin the algorithm (or small set) you accept. Reject none. Never let a token switch from an asymmetric algorithm to HS256, where an attacker could sign with your public key as the HMAC secret.
  • Skipping claim checks. Verify exp, nbf, iss and aud. A token issued for another service (aud) must not work on yours.
  • Weak HMAC secrets. HS256 secrets must be long and random; short ones can be brute-forced offline from a single token.
  • Secrets in the payload. Don't put personal data you wouldn't show the user, and never passwords or keys.
  • Long lifetimes. Keep access tokens short (minutes), and use a refresh token that the server can revoke.
  • Storing tokens in localStorage. Any XSS on your page can read them. An HttpOnly cookie can't be read by script, though cookies bring CSRF considerations instead.

Choosing

Use server-side sessions for a browser talking to its own backend. Use short-lived signed tokens when many independent services must verify identity without a shared session store, or across organisations, which is the case OAuth and OpenID Connect handle in the next topic.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.What does decoding a JWT's payload require?
  2. 2.A user's account is compromised. Their access JWTs are valid for 24 hours. What can you do immediately?
  3. 3.A verifier reads the `alg` field from the token header and uses whatever it says. Why is that dangerous?

0 of 3 right so far. Get all three to finish this topic.