If Your JWT Needs a Denylist, You Have Rebuilt Sessions With Extra Steps

JWTs are the default choice for new apps and usually the wrong one. The reason is revocation: there is no row to delete. And the standard fix, a server-side denylist, reintroduces the database lookup that was the entire point of going stateless.

Tech Talk News Editorial9 min read
ShareXLinkedInRedditEmail
If Your JWT Needs a Denylist, You Have Rebuilt Sessions With Extra Steps

Key takeaways

  • A JWT cannot be revoked before it expires, because verification is a signature check with no server lookup, so a token leaked at 09:00 that expires at 10:00 gives an attacker a full hour of access.
  • The usual fix, a server-side denylist checked on every request, reintroduces exactly the database lookup that statelessness was meant to eliminate, leaving you with session semantics and a larger cookie.
  • A session ID in a cookie with HttpOnly, Secure and SameSite set is at least as secure as a JWT for a same-origin web application, and revoking it is a single row delete.
  • Auth0, Clerk and Okta converged on the same pattern in 2026: keep the access token short, roughly 60 seconds to 15 minutes, and hold the long-lived revocable part in a refresh token in an HttpOnly cookie.
  • JWTs are genuinely the right tool for single-use tokens such as email verification, password reset and magic links, where the token is used once, expires quickly, and needs no revocation infrastructure at all.

Start a new project in 2026 and the default authentication choice is a JWT. It is what the tutorials use, it is what the starter templates ship with, and it sounds modern in a way that “session cookie” does not.

For most applications it is the wrong choice, and the reason is narrow and specific. Not cryptography, not algorithm confusion, not any of the classic JWT footguns. It is revocation.

A user clicks log out. An admin suspends an account. Someone changes their password because they think they have been compromised. In each case you want that credential dead now. With a session, you delete a row. With a JWT there is no row. Verification is a signature check performed on the token itself, with no lookup against anything, so a token that leaks at 09:00 and expires at 10:00 gives an attacker a full hour.[1]

1 row
What revoking a session costs
60s-15m
Access token lifetime the industry converged on
Full expiry
How long a leaked JWT stays valid
1 lookup
Per-request cost of a denylist, per request

The Denylist Undoes the Reason You Chose It

Everyone hits the revocation problem eventually, and the standard answer is a denylist: keep a set of revoked token IDs and check every incoming request against it.

Look at what that costs. You now perform a lookup against Redis or your database on every single authenticated request. That lookup is precisely the thing statelessness was supposed to eliminate. You have arrived back at server-side sessions, except your cookie is larger, your token carries claims that may be stale the moment after they are signed, and you are maintaining two mechanisms where one would do.

A JWT with a denylist is a session with extra steps and a bigger cookie. If you need the denylist, you needed sessions.

This is not a criticism of JWTs. It is an observation that a large share of applications adopt them for a property, statelessness, that they then give up in the first week of production, and are left holding the costs without the benefit.

What a JWT Actually Buys

The genuine advantage is that any party holding the public key can verify a token without calling you. That matters when several independent services must authenticate a caller and you do not want each of them making a round trip to a central auth service on every request. It is a distributed-systems solution, and it is a good one.

Ask honestly whether you have that problem. If your frontend talks to one backend that owns one database, you do not. You have a single service that could simply look up a session, and the network call you are avoiding is one you were never going to make. The same question is worth asking before most architectural splits, which is the argument in monolith versus microservices.

Why this matters

JWTs win on scale and portability across service boundaries, not on raw security.[1] Treating them as the more secure option is the category error that drives most of the bad adoption. The security properties of a session cookie are, for a same-origin web app, better, because instant revocation is worth more during a real incident than any property a signed token has.

The Boring Correct Default

For a normal web application: an opaque session ID in a cookie, backed by Redis, Postgres, or whatever you already run.[2] The cookie carries three attributes and each one closes a specific attack:

  • HttpOnly — JavaScript cannot read it, which defeats credential theft through cross-site scripting. This is the attribute people give up when they store a JWT in localStorage, which is the single most common mistake in this whole area.
  • Secure — never sent over plain HTTP, so it cannot be captured in transit. Worth understanding what that guarantee rests on: what the S in HTTPS actually buys you.
  • SameSite — controls whether the cookie is attached to requests originating from other sites, which is the primary defence against cross-site request forgery.

Set those three and a session cookie is at least as secure as a JWT for a same-origin app, and easier to revoke.[1] The database record is a single clear source of truth about whether a session is alive, which is exactly what you want at 3am when you are trying to establish whether an attacker still has access.

What the Identity Vendors Actually Do

Auth0, Clerk and Okta all settle this the same way, which is worth noticing given they have every commercial incentive to sell complexity. Keep the access token short, roughly 60 seconds to 15 minutes, and put the long-lived, revocable part of the session behind a refresh token held in an HttpOnly cookie.[1]

This is the honest synthesis. The access token is stateless and fast, and its blast radius on leak is bounded by an expiry measured in minutes rather than hours. The refresh token is stateful, revocable, and checked rarely, so the lookup cost you were avoiding happens once per refresh instead of once per request. You get the property you wanted on the hot path and the property you needed on the control path.

If you are going to run JWTs, run them like this. The failure mode is the twenty-four hour access token, which is stateless in the sense that a fire is self-extinguishing.

Where JWTs Are Genuinely Right

There is a class of problem they fit exactly: single-use tokens. Email verification links, password resets, magic login links.[1]

Every objection above disappears here. The token is used once, so a denylist is unnecessary. It expires in minutes, so the leak window is small. It needs to be verifiable by a service that may not share state with whatever issued it. And it needs to survive being pasted into an email and clicked from an unknown device. That is a signed, expiring, self-contained credential, which is what a JWT is.

Use them there without hesitation. The mistake is generalising from that fit to “JWTs are how authentication works.”

How To Decide

Three questions, in order.

Does anything need to verify this token without calling you? If no, use sessions. This eliminates most applications and it eliminates them at the first question.

Must revocation be immediate? If yes, you need server-side state regardless of format, so choose the format that expresses that honestly rather than bolting a denylist onto one that does not.

Is the token used once? If yes, a JWT is the right answer and the rest of this article does not apply.

All of this is about how you carry a session once you have one. How the token gets issued in the first place is a separate decision, and one where the answer has recently collapsed to a single flow: OAuth versus OIDC, and why PKCE is now mandatory.

For agents and machine callers the calculus shifts again, because the credential is long-lived and the caller is not a person who can re-authenticate. That is an authorization design problem more than a token-format one, covered in agent governance and RBAC, and the surrounding API risks are in the OWASP API list, where broken authorization outranks everything cryptographic.

Takeaway

Revocation is the deciding constraint, not security. A JWT cannot be revoked before expiry, and the denylist that fixes it reintroduces the per-request lookup that was the entire point of going stateless. For a same-origin web app, an opaque session ID in an HttpOnly, Secure, SameSite cookie is simpler and revokes in one row. Keep JWTs for single-use tokens and for genuine cross-service verification, and if you run them for sessions anyway, keep the access token to minutes and put the revocable half in a refresh cookie.

Sources and further reading

Practitioner and vendor guidance current for 2026. Where sources disagree on emphasis, the revocation argument is the one they all make.

  1. 1.ReportingStytch, "JWTs vs. sessions: which authentication approach is right for you?". Source for the revocation window on a leaked token, the short-access-token plus refresh-cookie pattern, and the single-use token cases where JWTs fit.
  2. 2.Reporting"Stop defaulting to JWTs: choosing the right session architecture in 2026". Source for the opaque-session-ID-in-an-HttpOnly-cookie recommendation as the pragmatic default for web applications.
  3. 3.PrimaryOWASP API Security Project. Context for where authentication sits among the wider API risks, most of which are authorization rather than credential-format problems.

Frequently asked questions

Should I use JWT or sessions for authentication?
For a normal web application where your frontend and backend share an origin, use server-side sessions with an opaque session ID in an HttpOnly cookie. JWTs earn their keep when several independent services must verify a token without calling a central auth service, which is a scale and architecture problem rather than a security improvement.
Why can't you revoke a JWT?
Because verifying a JWT is a signature check performed entirely on the token itself, with no lookup against any server-side record, so there is nothing to delete when you want to end the session. The token stays valid until its expiry, which is why a leaked token grants access for the remainder of its lifetime unless you add infrastructure specifically to stop it.
What is wrong with using a JWT denylist?
Nothing is wrong with it except that it undoes the reason you chose JWTs, since checking a denylist on every request is a database or cache lookup on every request, which is exactly what stateless verification was supposed to avoid. At that point you have session semantics with a larger cookie and more moving parts, so plain sessions are the simpler expression of the same design.
Are session cookies less secure than JWTs?
No, and for a same-origin web app the reverse is usually true. A session ID in a cookie marked HttpOnly, Secure and SameSite cannot be read by JavaScript, never travels over plain HTTP, and is not sent on cross-site requests, while it also revokes instantly, which is the property that matters most during an actual incident.
What are HttpOnly, Secure and SameSite?
They are cookie attributes that each close a specific attack. HttpOnly blocks JavaScript from reading the cookie, which defeats token theft via cross-site scripting; Secure prevents the cookie being sent over unencrypted HTTP; and SameSite controls whether the cookie is attached to requests originating from other sites, which is the main defence against cross-site request forgery.
When is a JWT the right choice?
When the token is used once and never needs revoking, such as an email verification link, a password reset, or a magic login link, since a short expiry does all the work that revocation would. The other genuine case is many independent services needing to verify a caller without a round trip to a central auth service, which is a real distributed-systems problem rather than a default.

Written by

Tech Talk News Editorial

Computer engineering background. Writes about software, AI, markets, and real estate, and the places where the three meet.

More about the author
ShareXLinkedInRedditEmail