Hacker News new | ask | show | jobs
by Ninn 3032 days ago
> JWT is great until you get to the point where you want to have things like token revocation.

What a flawed argument, there are techniques that allows for session revocation, even in an async stateless jwt context, i.e. By blacklisting, which will work great, and give you some nice properties, depending on your infrastructure and design.

Sadly, some appear to assume jwt is some special solution that does X right and y wrong.. but its really nothing other than a structured format in the end. But surely a lot of people do a lot of wrong stuff when deploying their stuff on top of jwt.

2 comments

Yup, this board is full of JWT hate mostly predicated on the fact that it can be done wrong. Just use a random token as a session ID. Wrap in cookie for browser users and use browser session cookie expiration. Wrap in JWT and sign an expiration date in there for API use...no other state needed, use token to look up actual valuable state on server side as necessary. Expire them server side too based on application re-login requirements to prevent reuse (or sign your cookies w/ an expiration date like you do with JWT...but I always keep session tokens and expire them on the server side too for various reasons including auditing purposes).
And what does that give you over just using a cookie?
Sometimes you don't have cookies? Like with mobile apps. With JWT you can also have uniform auth across mobile and web apps, and when done right is a beautiful thing™.

Also cross domain/app data signing.

> Sometimes you don't have cookies? Like with mobile apps.

A cookie is just an HTTP header. Any mobile app that can speak HTTP can use cookies.

> What a flawed argument,

Touché!

> session revocation, even in an async stateless jwt context

> blacklisting

But isn't blacklisting stateful in its nature and thus achieving the opposit of what JWTs are for? Am I missing something obvious?

There's always state. Stateless is a misnomer, in much the way that serverless is a misnomer. It typically indicates the state information is passed inlined with the request.

When you inline the state in a cookie for "stateless" operation, you are essentially operating on a (hopefully cryptographically secured) cache.

Revocation is thus a cache invalidation, and requires its own state. When revoking from a database or memory, you can simply delete or mark the record. When revoking cookies, you are going to build that state mechanism as a blacklist.

This is why OAuth lets you have both access and refresh tokens. Your access tokens can have inlined state with a short duration, such that an API can service a request without having to be bottlenecked in a database or API call against an authoritative source.

However, when that access token expires (say, after 10 minutes), the API will stop accepting it. That is when you have to use the refresh token, which goes back against the authoritative source.

You lose the immediacy of a blacklist, but you don't have to have that state distributed across your infrastructure. You instead wind up pushing the effort to keep up-to-date tokens onto the OAuth clients.

I think you have this right. This was also my experience with JWTs - go far enough down the revocation rabbit hole and it seems you just end up with a stateful solution again, but just with a more complex and expensive token verification mechanism (compared to just equality checking the token value). At that point, it really seems pointless.
>you just end up with a stateful solution again

It checks the "stateful" box in a nominal way, but it does not have the drawback of stateful session cookies that "stateless" defines itself in comparison to: in the backend, the session is still not in-memory or in-db on a single machine.

So you don't really go back to "stateful" except nominally; a very large part of the scaling benefit remains.

Let me be more concrete, maybe it will illustrate the point I'm trying to make better and/or highlight some aspects I'm wrong about.

I'm aware of broadly two schemes for revocation support with JWT:

1) Immediate revocation - keep a blacklist in state on a single machine. On verification of the JWT, check the blacklist and only succeed if it is not present.

2) Eventual revocation - keep a renewal token in state on a single machine, and give your JWTs an expiry time T. On verification of the JWT, if verification fails due to expiry then try to renew the JWT using the renewal token, and if successful then succeed the verification and also return the new JWT to the client. To revoke the JWT, just revoke the stateful renewal token. The JWTs can therefore be revoked eventually within a max time of T.

--

So, the above Vs the classical stateful session token (from here SST) approach:

Both 1) and 2) contain the same 'single stateful machine' limitation as SST, yet are more complex to understand, reason about and maintain.

In 1) you get no additional benefits over SST, and you have completely nullified the statelessness and therefore scalability of JWT. You also get the additional downside of a more computationally expensive token verification than just a simple equality check.

Conclusion - it seems irrational to use 1) over SST in all cases.

--

In 2) you get precisely one potential benefit for scalability - that for a time (max T) you get a stateless 'cache' for the verification of the JWT, which will horizontally scale to infinity. This MAY give you some practical benefit at massive scale, but there are two significant downsides:

A) You have a max lag T on revocation of the JWT, which is a security hole if the JWT is stolen. So you'd like to minimise T - but not so much that you lose the benefit of the 'cache' - otherwise the whole thing is pointless. You have to literally trade off security for scalability! Where do you draw that line?

B) The whole system is very much more complex to operate, debug, and reason about, than SST. You have verification logic that is split across 1 central stateful renewal token server & n JWT verification servers.

Conclusion - the scalability gain of 2) comes only as a trade off against security. Even assuming you are able to accept this trade off at all it is really unclear to what extent you should do so, and indeed if the whole complexity of the thing is even worth it after all that.

--

Perhaps there is another way to do revocation of JWTs that I've missed that is clearly superior to SST, maintaining stateless verification without any clear security trade offs. If so please educate me - I would love to discover it!

Otherwise, I concede that scheme 2) might be a better option than SST in some very rare and special cases, but in general if revocation is needed, then JWT does not make sense in place of SST.

I'm using JWT for auth on a microservice so that it doesn't have to phone home to authorize requests. For revocation, the user service pushes the revocation to the microservice, which maintains its own blacklist. JWT works great for us because it contains all of the identity necessary for auth decisions on the microservice. An opaque token would require the microservice to phone home to get a user's identity data.
I appreciate the effort you put into writing your response. You make a very solid case and I wouldn't attempt to talk you out of using SSTs over JWTs given the level of consideration you've put into it.

I would nonetheless offer the following points if you'll excuse the rambling:

- I concede that the following probably exposes a lack of knowledge on my part. I think most, myself included, don't have a 100% lucid understanding of how session cookies are managed throughout the stack. It feels like there is some degree of idiosyncrasy and magic at every level, e.g. client -> reverse proxy -> application backend (-> potentially DB) while JWT feels much more lucid and consistent in this regard. It seems desirable and simpler to want to transparently pass through all the "smart" layers that have explicit knowledge of and opinions about session cookies.

- JWTs come off less "magical" than session cookies and I find them just as easy, if not easier to reason about in certain situations, e.g. in an app rather than in a browser - I'm aware that cookies are mere headers, but I still find that slightly more annoying to manage than JWTs - although I concede that perhaps this is merely due to the availability of libraries that make it so, while also not being too "magical" and hiding key parts of the flow (whereas various platforms e.g. ASP.NET, PHP do weird magical things with session cookies).

But, you know, your post is making me have second thoughts about my position here, and I may yet be won over after thinking about this some more. I think this is often an underrated and unnoticed point - that there is some a priori magicalness about session state, while JWT is simply presented less magically, and so one may be attracted to the initial lucidity of JWTs vs. having to learn session state (because "why haven't I learned how automatic session management works after using it for all these years? It's probably very hard"). And I'm beginning to realize choosing JWTs for this reason may be specious.

- I find, in practice, that a JWT workflow's blacklists are still a lighter level of statefulness than having to propagate user sessions. As you rightly state, this comes in the form of eventualness, and it is indeed a security tradeoff. Here, perhaps my imagination (or experience) is lacking, but I estimate that it would in practice be difficult using SST for 1. To have a token theft; 2. To detect said token theft; 3. To implement security measures; in less than the time of JWT revocation.

- I obviously find that JWTs make more sense than session cookies in RESTish APIs, in which the desired workflow is modeled after HTTP and does not require a session on the backend (so the statelessness is not just for scaling). It's certainly a good thing to have a standard for passing a verifiable proof of authentication inline with every request.

> "But isn't blacklisting stateful in its nature and thus achieving the opposit of what JWTs are for?"

JWTs are really just the format, and you can store them however you want to.

What you want is to avoid having to query a centralized database or datastore for every request. (1)

It's hard, but doable, to design a blacklisting scheme that does not depend on a centralized db (2). This way you can have your cake and eat it too. You avoid the "ask the database for every request" scenario. People will counter with "but you query the database for every request anyway" to which I answer: "No, I do not" :)

1: Well, that depends on your app and usecase, of course

2: Basically you push the blacklist, which is short, out to everything that needs it. It gets complicated =/

JWT's are just special signed formatted strings with a couple of dots in the middle. I only use them for API tokens, and I don't use them statelessly, I just use them so the client knows the format and can check expiration inside it. Sure many use them to pass around signed state, but that's a choice. They're just a container for a stateful session ID for me.