Skip to content

JWT and Session Tokens Explained: Stateless vs. Stateful Authentication

Two articles back, we covered cookie+nonce authentication, and last time we looked at how WordPress’s login cookie is actually a hybrid: signature verification is fully self-contained, but revocation depends on a server-side list of valid tokens. This time we look at the opposite end of the design spectrum: JWT (JSON Web Token), which aims for authentication that is fully stateless — no server-side token list at all. Comparing it against WordPress’s hybrid model makes the trade-off between stateful and stateless authentication much easier to see.

What a JWT actually is: three dot-separated parts

A JWT looks like a single string split into three parts by dots — xxxxx.yyyyy.zzzzz. These correspond to a header, a payload, and a signature. The header names the signing algorithm (such as HS256) and token type; the payload holds “claims” — pieces of data like a user ID, an expiration time, or permissions. The header and payload are each Base64url-encoded and joined with a dot, and the signature is computed over that combined string using the algorithm named in the header.

Note: Base64url is a variant of ordinary Base64 that swaps out characters like +, /, and = for - and _ so the result is safe to embed in URLs and cookies. It’s just a character encoding, not encryption — anyone can decode it back to the original string instantly.

A common misconception: “signed” is not “encrypted”

A frequent misunderstanding is assuming the JWT payload is hidden because it’s “encrypted.” In reality it’s only Base64url-encoded — there’s no encryption involved at all. Anyone can decode it in a second using browser dev tools or a command line, and read the user ID, permissions, or any other claim inside directly. What a JWT actually guarantees is not confidentiality but integrity: that the content hasn’t been tampered with since it was issued. This is the exact same property WordPress’s authentication cookie relies on via its HMAC signature, as covered last time. The practical takeaway is the same in both cases: never put a password, API key, or other secret inside a JWT payload.

No database lookup required — genuine statelessness

WordPress’s authentication cookie, as we saw last time, checks the HMAC signature and then also has to look up whether the token is still on the list of currently valid tokens stored as user metadata in the database. That lookup is exactly what makes “log out everywhere” possible — but it also means a database query happens on every single request.

JWT is designed to skip that lookup entirely. Whoever needs to verify a token only needs the shared secret key (for HMAC-based signing) or the public key (for asymmetric algorithms like RSA or ECDSA) to recompute the signature and check it matches. No database query, and no round trip back to the issuing server, is required. This is exactly what “stateless” means here, and it’s why JWT is popular in distributed systems where multiple microservices need to share the same authentication scheme — each service can verify a token entirely on its own, using only a key it already holds, without querying a central session store.

The trade-off: no way to instantly revoke a single token

This statelessness comes at a clear cost. Once a JWT is issued, it will pass verification anywhere its signature checks out, right up until it expires — there’s no equivalent of WordPress’s “log out everywhere” for a plain JWT. The very design choice that eliminates the database lookup — not maintaining any server-side list of valid tokens — is also what removes the ability to forcibly invalidate one specific token before it expires.

Real-world compromises: pure statelessness is hard to sustain

Two common approaches address this weakness in practice. The first is to keep the JWT’s own expiration very short — a few minutes to maybe an hour — and pair it with a separate, longer-lived “refresh token” that the server tracks and uses to reissue new JWTs periodically. This reintroduces a small amount of server-side state, in the form of managing the refresh token, but the short-lived JWT itself can still be verified statelessly. The second approach is to maintain a server-side blocklist of revoked token IDs (the jti claim) and check every incoming token against it — which, in effect, brings back the same kind of token-list lookup WordPress relies on, giving up most of the statelessness advantage in the process. Between these two compromises, it becomes clear that being fully stateless and being able to revoke instantly are, in principle, difficult to have at the same time.

Which one fits — it depends on the system’s shape

In a system like WordPress, where a single central database already sits within reach of every request, the hybrid approach we covered last time makes good sense: the database round trip is already happening anyway, so adding one more lookup for the token list is a small cost for a genuinely useful feature — instant revocation.

In a distributed system made of several independent services, or a mobile app talking to multiple separate backend APIs, having every request check in with one central session store can itself become a bottleneck or a network dependency. That’s where JWT’s “verify independently with just a key” property becomes the real advantage. Giving up instant per-token revocation in exchange for a short expiration plus a refresh token often makes the overall system easier to operate at that scale.

Summary

A JWT is made of a header, a payload, and a signature; the payload is merely encoded, not encrypted, so it should never carry secrets. Its defining feature is genuine statelessness — verification relies purely on a signature check, with no server-side token list required — which is what makes it cheap to verify across distributed services. The cost is that no single token can be revoked instantly, which is why real deployments lean on short expirations paired with refresh tokens, or a blocklist that partially reintroduces state. Seen next to WordPress’s hybrid cookie model from last time, the two designs come into focus as different answers to the same underlying tension: fast, stateless verification versus the ability to revoke access on demand.