Authentication over HTTP

Choose how a request proves who it is, and answer correctly when it cannot.

10of 18
≈ 13Min read
5Parts

HTTP has no memory

Every request is independent. "Logged in" means only one thing: the request carried something the server recognises. There are three somethings in common use, and the choice between them is about where the state lives and how you revoke it.

Carried asState livesRevoking
a session id in a cookieon the serverdelete the row — instant
a signed token (JWT) in a cookiein the tokennot until it expires, unless you keep a deny list
Authorization: Bearer …in the tokenthe same problem, plus you must store it somewhere

Where a token may be kept

If an attacker gets script running on your page, localStorage is readable and an HttpOnly cookie is not. The cookie can still be used by that script — it rides along with a fetch — but it cannot be copied out and used elsewhere, which is the difference between one hijacked tab and a stolen account.

The usual argument for localStorage is "cookies mean CSRF". SameSite=Lax closed most of that, and a CSRF token closes the rest. The argument for tokens in a header is a genuinely cross-domain API — which is a real case, not the default one.

the only two real options, and one of them is not one
// an HttpOnly cookie: script cannot read it, sent automatically
Set-Cookie: sid=…; HttpOnly; Secure; SameSite=Lax; Path=/

// localStorage: any injected script reads it in one line
localStorage.setItem("token", token);      // one XSS and the session is gone
Short-lived access, long-lived refresh

An access token that lives fifteen minutes limits the damage; a refresh token in an HttpOnly, SameSite=Strict cookie scoped to the refresh path gets you a new one. That pair is the standard answer to "tokens cannot be revoked".

The two status codes, again

A client typically reacts to 401 by clearing its session and sending the user to the login page. Answering 403 with a 401 therefore logs people out for opening a page they were never allowed to see — a bug report that looks like "it keeps logging me out at random".

and the header 401 is required to carry
401 Unauthorized            # I do not know who you are
WWW-Authenticate: Bearer    # …and this is how to tell me

403 Forbidden               # I know exactly who you are, and no

Logging out, and what it must do

  • Delete the session on the server. A cookie the client throws away is not a logout.
  • Clear the cookie with the same name, path and domain, and Max-Age=0.
  • Rotate the session id on login as well — otherwise an id an attacker planted before you logged in is now an authenticated one (session fixation).
  • On a password change, invalidate every other session.

Things that are not authentication

A hidden field, a header your own app sets, a check in the front end, an id that is hard to guess. All of them are bypassed with one curl. The server decides, on every request, from something it can verify — and CORS, as the last lesson said, is not part of that decision.

Practice