Hacker News new | ask | show | jobs
by lynndylanhurley 3661 days ago

    You cannot invalidate JWT tokens
        

    This is simple not true. You ALWAYS will sign your tokens with a well known secret, you could eventually even add some salt from a database to it.
I think one of the benefits of some (most?) JWT implementations is that it doesn't hit the database on every request - the token is only validated against a global secret.

So you can't invalidate a single user's session without invalidating all users' sessions.

5 comments

I keep a counter in the JWT to at least mostly get around this issue. When processing a request, the counter is checked for the user, which isn't a big deal since all of the requests already require looking up the user. A counter increment invalidates all of that user's existing tokens.

If a user changes their password, their roles change, etc, then the counter gets incremented so all tokens issued up to that point won't be valid anymore.

Can you explain this a little bit more? You keep a counter on the user object in the DB? What is the JWT buying you if you still have to hit the DB on every request?
Presumably you can keep counters like these on the server edges, and just push new values out to servers whenever things change, as opposed to query a DB every time. This wouldn't invalidate individual tokens however, but all tokens that have that counter value. It'd also mean there's a window where tokens can still be used while servers are being updated with the new value(s).

These are just some random and half-baked thoughts, I have no idea what OP does, but there are options to limit hitting backing DBs anyway.

Yep ^ This is what I often do too. Simple, but effective.
That's pretty smart. Effectively versioning your issued tokens. Way easier than maintaining a blacklist.
You can store the user info in the JWT so you don't need to hit the database to get user info every time. I usually just store an id in each issued token and store/remove it from redis or memory as needed for invalidating it.
You have to be careful that you are not leaking sensitive info though, as the JWT payload is meant to be visible on the client as well.
Yes you can. The system I built uses a revocation list (propagated to all servers in the cluster) to invalidate the session.

Just as if you were storing state on the server, it still requires a means of propagating the user's state to every server. But we only need to propagate a single ID once per session (at logout) instead of propagating all the session data to all the servers on every request.

To revoke with a revocation list, you need to know the ID of the token being revoked. That only works if (a) the token is present at the event that triggered the revocation, or (b) you are tracking all tokens, in which case there's probably not much point to using JWTs at all.

Another approach is what I call the "subject epoch" pattern. The subject of a token (the "sub" JWT claim) is often something like a user ID. When an event occurs that requires all tokens for a given subject to be revoked, save that time stamp as the subject's "epoch". When processing JWTs, those issued before the subject's epoch must be considered invalid.

Do you ever worry that, in the case of failure or some other event, your revocation list could be lost and allow old hacked sessions to be used?

Is there a way around this? I like the idea of a revocation list, but this seems to be a pretty big concern.

Not particularly. There are other layers of security here - the sessions expire (so there's a limited window to exploit each one), the sessions are always transmitted via SSL (so you pretty much have to have an exploit on the customer's system to get one), and the sessions are restricted to one customer (so you only have an attack against the customer whose system you have an exploit on).

If we used a different approach, then the same error (losing the data that's being synched) would result in losing all of the customer sessions.

The same problem occurs on every other method. i.e. Bearer/Cookies. As already said the most vulnerabilities applies to all methods. But people are just too blind to see that.
"old hacked sessions to be used"

JWTs generally contain an expiration timestamp.

revocation list only needs to contain tokens that haven't expired. If there is an 'event' that causes this info to be lost, then expire everything by changing the global secret.
Out of curiosity, how are you propagating the IDs to other systems in the cluster? I am building something which could benefit from pushing config data across a cluster.
In most cases it shouldn't matter; revocation lists ought to be trimmed to the lifetime of the issued tokens--when they are used at all, revoking a JWT rather than just letting it expire is likely not extremely common--so you could stuff them anywhere at all that is convenient to your other technology selections.
it's my understanding something like Redis is good for cross cluster im-memory persistence/reference
I hit the database on requests - I keep their identity in the JWT but not their permissions. And if they're hitting a protected route (the only time their identity is necessary anyways) you best be sure I'm checking their canonical permissions.
You might be, but many people use signed cookie sessions in order to avoid having to check the DB.
Can't you maintain session state in a Redis daemon and invalidate it there? Or are you talking about stateless JWT?
You include the time created in the object before signing, and invalidate based on that as a timeout.
Sure, but that's expiration, not invalidation, ie. we're talking about the ability to declare "this particular token is invalid now".

Incidentally, this limitation isn't too surprising to me... Is there any possible token-based authentication scheme that is both stateless (ie. no round trip to the database on every call) AND invalidate-able? Seems like any form of invalidation would require storing the "is valid" state somewhere else...

> Is there any possible token-based authentication scheme that is both stateless (ie. no round trip to the database on every call) AND invalidate-able?

I suspect this is provably impossible.

There are all sorts of things you can do at the protocol/platform level if you have a shared secret, but with only the constraints of an open authentication scheme you lack the tools to do this.

If you're willing to delve into the fun world of CRLs you can sorta do it. This isn't truly stateless of course, but for some design constraints it could be "practically" stateless since you're eliminating auth server round trips, which is probably why you were aiming for statelessness in the first place.

CRLs of course introduce lots of replication complexity and timing bounds to consider, and you probably want to pair them with short lived tokens to keep the CRL size manageable. (and then delve into refresh tokens)

As the OP points at, you most likely don't need any of this.

Eventually JWT has very little Roundtrips.

Our current Implementation is:

- Really short lived JWT of 1 minute - If the JWT is invalid and the user didn't do a request in the last minute it does query the database for a session token (we use session tokens and jwt). if the Token is inside the database/redis/ehcache/whatever the user gets still a new JWT token.

Actually we did that since we needed a "sane" way of revoking tokens fast but still keep the user logged in until the browser is closed.

We don't have a mobile client (yet) but I guess we try to do something like that, too. just not with a session. This works really well and mostly our users won't hang around for more than a minute and when they do its not a problem to have a single backend call.

> stateless (ie. no round trip to the database on every call)

AFAIK, Stateless is about independent request/response and not needing a server to retain session information through the course of multiple requests. It has nothing to do with whether or not you're checking a database to cross-reference credentials - and I wouldn't keep anything more than an ID/name in a JWT... ever.

https://en.wikipedia.org/wiki/Stateless_protocol

JWT is good for much more than an ID+name; it's sensible to allow your partially-trusted token issuers to vouch for a _limited_ set of user roles and the like. Just the same way you probably wouldn't allow the @acme.com security authority to vouch for @example.com principals in a multi-tenant system... in ye olde SAML lingo this is called "claim filtering / transform / passthrough."

Practically speaking, we use a bit of metadata (jsonb documents in pgsql, mostly) for each JWT-issuing party, which describes how to validate principals, how to map incoming claims to "our" claims (e.g., "by what name does sts.acme.com call a 'role'? is sts.acme.com allowed to vouch for the 'admin' role?) in addition to the more central things like shared secrets, certificates, etc. This kind of partial trust is how claims auth is supposed to work, and avoids any unnecessary provisioning/syncing of user details.

> Is there any possible token-based authentication scheme that is both stateless (ie. no round trip to the database on every call) AND invalidate-able?

SPKI (RFC 2692/2693) solved this in 1999, with its timed CRLs. Exactly one CRL is valid for any given period of time: when a token (= certificate) is received, it is invalid if it is referenced in the CRL, and valid otherwise.