Should a project use sessions or JWTs? One isn't right or wrong, it all depends on the context of the project.
Do you maintain a database of JWT session tokens for refresh and revoke?
Do you have a real session that you load for every user every request anyway?
If the answer is 'yes', then the answer to 'use JWT' isn't 'it depends'.
It's no.
JWTs have absolutely been over-hyped for the last 8-10 years, but they do have a use and you don't have to be at the scale of Google for it to be the right approach.
Software isn't as simple as creating a checklist of a few basic categories and saying there is always a right or wrong answer. The answer should be "it depends" because there are many more factors at play when deciding something as fundamental as authentication and authorization.
For starters, the blog post leads with the premise that the answer is always "No". Even ignoring that as playful banter, the checklist is way to broad to be universally true.
> You wanted to implement log-out, so now you’re keeping an allowlist of valid JWTs, or a denylist of revoked JWTs. To check this you hit the database on each request.
> You need to be able to block users entirely, so you check a “user active” flag in the database. You hit the database on each request.
> You need additional relationships between the user object and other objects in the database. You hit the database on each request.
> Your service does anything at all with data in the database. You hit the database on each request.
These all assume that I authorization data and that the user data lives in the same database. I may store my own user object with a unique ID that references an authenticated user, while the author flow is actually managed by an entirely different service (whether first party or third party). I also may have my user data stored in a separate database, say for legacy reasons or data security/integrity requirements. Not every database is crested equal, its totally possible that one database is a better fit for user data while a totally different database is needed for application data.
Coming up with a list of broad scenarios and claiming that if you check any one there is never a reason to pick JWTs over sessions is misguided and overly simplistic. Context always matters.
edit: formatting
Please tell us how the following assumes that authorization data and user data lives in the same database:
"You wanted to implement log-out, so now you’re keeping an allowlist of valid JWTs, or a denylist of revoked JWTs. To check this you hit the database on each request."
If authorization data is stored outside my database, I may not store anything related to that user other than their unique ID. I may not know anything about the JWTs because a middleware talking to the external authorization service authorizes a request before I ever query my database.
I could also store valid JWTs in my own database or in a caching layer, say with redis for quick access. There's nothing wrong with that, but without more details on what someone is building and what it needs to be designed for there is no absolute right or wrong answer.
How you organize your data, your services, your middleware, etc. does not change this the slightest, hence are irrelevant.
----
[1] proper logout is instant (this invalidates the "just use a JWT with short timeout!" argument), and prevents session stealing (this invalidates the "just throw away your JWT to logout" argument)
Isn't that kind of the point though? I'm not saying JWTs are a better answer for logout, only that JWTs can still support logout and they both similarly require a database transaction.
If logout can work either way, and in all likelihood logout isn't a performance concern as long as its secure, you may have other use cases and requirements in an app that do make JWTs a better fit.
I still don't understand how the argument here can be that there are no cases for JWTs, or that the list of "its" in the original article can be bulletproof no matter what else is going on in a project. That's unnecessarily absolute and leaves no wiggle room for competing priorities and constraints.
Because the whole selling point of JWTs was that they eliminate this database transaction!!!
As soon as you have logout, your JWTs degrade to nothing but a unique byte sequence that you need to check in the DB whether it's still valid or not. You may as well use unique JPEGs or MP3 files to identify your sessions - it would work just as well (and it would be equally stupid).
In fact, in the presence of logout, JWTs are worse than opaque session tokens, because careless programmers developing a new microservice might see that it's a JWT and be tempted to just check the signature and the expiration without consulting the auth database for revocations, thus leaving that microservice accessible with compromised tokens.
For authentication and authorization there isn't much benefit, but that doesn't make one right and one wrong. They can be useful in certain author scenarios, like if you're using an external author service.
If the argument here is simply that JWTs aren't a huge benefit in auth, sure that's fine. I just don't agree that that means they should never be used for auth or that they are universally worse for auth in all scenarios and applications.
Thank you. This middle ground between hyperscaler infrastructure and super simple web apps is where most of my career has been spent, yet the recent trend is to pretend like there are only two possible extremes: You’re either Facebook or you’re not doing anything complicated.
It has an unfortunate second order effect of convincing people that as soon as they encounter something more complicated than a simple web app, they need to adopt everything the hyperscalers do to solve it.
I wish we could spend more time discussing the middle ground rather than pretending it’s some sort of war between super simple or ultra complex.
If we're talking about a web session, time-limited randomly-generated session tokens that are stored in a DB still work fine. If you really need it, put a caching layer (memcached or redis or valkey or whatever) in front of it. Yes, then you've created cache invalidation problems for yourself, but it's still less annoying than JWT.
If we're talking about authenticating API requests, long-lived randomly-generated auth tokens stored in a database work fine, generally. (But allow your users to create more than one, and make rotation and revocation easy. Depending on your application, allowing your users to scope the tokens can also be a good thing to do.) Again, put a caching layer in front of your database once you get to the scale where you need it. You probably won't need it for a while if you're sending your reads to read-only replicas.
(Source: worked at Twilio for 10 years; we definitely eventually ran into scaling problems around our user/auth DB, and our initial one-auth-token-is-all-you-need setup was terrible for users, but these problems were fixed over time. Twilio does use JWTs for some things, but IMO that was unnecessary, and they created more headaches than they solved.)
I'm not saying no one ever needs JWTs, but I think they're needed in far fewer circumstances than most people think, even people who agree that JWTs should be looked upon with some skepticism. If you need to be able to log people out or invalidate sessions or disable accounts, then JWTs are going to create problems that are annoying to solve.
(One possibly-interesting solution for JWT-using systems that I haven't tried anywhere is to do the reverse: don't cache your user/auth database, but have a distributed cache of JWTs that have been revoked. The nice thing about JWTs is that they expire, so you can sweep your cache and drop tokens that have expired every night or whenever. Not sure how well this would work in practice, but maybe it's effective. One big problem is that now your caching layer needs to be fail-closed, whereas in a system where you're caching your user/auth DB, a caching layer failure can fall back to the user/auth DB... though that may melt it, of course. I also feel like it's easier to write logic bugs around "if this record is not found, allow" rather than "if this record is not found, deny".)
You just (somewhat handwavingly) described what Google and Facebook are doing. You might not need to build this globally highly available distributed session store, JWTs might be an ok solution for your use case too (because you are not Google or Facebook) - or not. It depends on what your requirements are. AuthN across services is somewhat complex in any case, I don't think there is an easy way around it without making tradeoffs somewhere. JWTs are a great tool to consider here.
This works fine for a single service but you’re replying to a thread about the middle ground of multiple services. It’s an anti pattern to have every service talk to the same database just to authenticate every request.
By the time you add a caching layer you’re truly better off using an off the shelf oidc id provider and validating the id token claims.
Postgres has insanely good read performance. Most companies and services are never going to reach the scale where any of this matters, and developer time is usually the more precious resource.
My advice is always, don’t get your dev team bogged down supporting all this complicated JWT stuff (token revocation, blacklisting, refresh, etc) when you are not Facebook scale / don’t have concrete data showing your service really truly needs it.
For mostly-read flow like authentication, a centralized database can scale really well. You don't even need postgres for that.
If you have mutable state, JWT can't help you anyway.
JWT start make sense only when you are doing other hyperscaler stuffs and you can reuse part of those architecture
I was inspired by https://philip.greenspun.com/panda/ circa 2001 to develop a complete user management framework based on that kind of cookie which had the advantage over other systems that the “authentication module” it took to get authentication working in a new language was maybe 40-100 lines of code. Software like PHPNuke that combined second or third rate implementations of apps all in the same codebase was the dominant paradigm then, the idea that you could pick “best of breed” applications no matter what language you were using was radical and different.
I used the framework for 10+ projects, some of which got 350,000+ active users. As an open source project it was a complete wash. Nobody got interested in user management frameworks (as opposed to writing your own buggy, insecure and hard-to-use auth system in a hurry) until around 2011 or so when frameworks based on external services all of a sudden popped up like mushrooms. Seemed like the feature I was missing was “needs to depend on an external service that will get shut down with the vendor gets acquired”
A simple JWT implementation isn't that complicated, but you have to accept some limitations.
Bullshit.
If you’re having trouble with that, you’ve got bigger issues. Any regular work queries will take longer, and so it’s not even a meaningful area of concern if you broke down a request from end to end on a flame graph.
Yeah, so? They don’t have to be talking to the same system, and in fact it was literally what you called bullshit to originally.
> If you’re having trouble with that, you’ve got bigger issues.
That does absolutely nothing to changing the fact that a SPoF is still an anti-pattern that should be avoided.
For that matter…
> A in-memory cache on top of session retrieval is so trivial and adds so few microseconds that it’s imperceptible even at large volumes of traffic.
Also does absolutely nothing to change that fact. You have done nothing to actually elaborate on why it’s totally not a horrendous idea to have everything communicate to the same database. Just because there’s a caching layer does not mean that fresh data wouldn’t be available if a SPoF goes down, which, once again, is the whole point here.
This statement that doesn't mean anything.
If you want people to believe what you say, you have to actually explain why you think something. Just saying it does not somehow make it true.
Be better.
But that also means that you can have JWTs that are used as "random token" for most of your app, cost to produce them isn't high, and only make use of the additional capacities for instance when
- when you want to check signatures (e.g. reject before hitting your application layer)
- store non sensitive base64 data that you want before restoring the session
Creating and handling JWT is only as costly and complicated as you want it to be, so there's IMHO enough flexibility to have light use with very few penalities for it.
It's shocking how often this advice isn't followed. We often see it with non-tech companies who nonetheless deliver services over the internet.
Every cache has TTL, so you just set the TTL of the entry to the expiration date of the token you are caching. No need for nightly cleanups.
Revocation of tokens can be done for a simple logout operation, in which case the stakes are low, but more often it is the "pull the fire alarm and get that user out", and in that case it should be reliable.
Back in the day we used memcached for our primary store for all sorts of ephemeral things. Including user sessions.
Thing is, token refresh service can be stateless, but adding a revoke service basically kills JWTs main advantage, since every time we check its validity, we need a query to see if its been revoked.
But since such a revocation list is going to be short (usually 0 entries, dozens at worst), it's trivial to replicate across all the auth service nodes (which can as well be worker nodes) or keep it in Redis replicated per DC, with sub-millisecond lookup times.
Things get harder if you want a feature like logging out other sessions, or just an explicit logout on a shared computer (think about business settings: stores, pharmacies, post offices), you may have to have larger revocation lists. This may still not be a problem: a million tokens is a few dozen megabytes, again, a per-DC replicated Redis cluster would handle it trivially and very cheaply.
I'm of a more gradual opinion regarding this - say you operate a movie streaming service and control access to movies via JWT. It's not a problem if an attacker has access for two more minutes than intended.
If you are talking to a single client, I think checking the remote IP address and encoding it in the token might work to see if the token is not stolen, but don't quote me on that.
I get that this has conceptual appeal, but I doubt this makes any difference in real life. Unless you have some very sophisticated infrastructure, it takes many minutes to discover the issue and then many more minutes even to decide what to do about it. A few extra minutes to cut off access is probably not going to make a big difference one way or another.
When I had a chance to design a token-based authn/authz system, we had two types of tokens, general access (with hours of expiration, mostly read-only access) and privileged access, with expiration time set to a minute or so. All auto-refreshed on use, all separately revokable.
Seems to me that for most applications, the irrevocable 5-minute token seems "good enough".
To revoke a token:
1. issue a new token to the revoker that is issued at current time (if business rules require revoker to be logged in).
2. set user inb to current time - 1 second with a TTL of longest issue * 1.5.
3. Add user to bloom filter.
4. upload bloom filter to s3, every service downloads this every 5 minutes.
5. Then on request, check bloom filter. If the user id is in the bloom filter, check with revocation service that inb > issued time.
This is probably less than five hundred lines of code and pretty easy to maintain.
It also makes e2e testing very easy since you should be logged out after pushing that button.
But yeah, if you have a revoke service, might as well just use session keys.
Edit: typo
It’s not necessarily easier than just passing the jwt, but with our internal setups where when you first pass through the authorisation system, our traffic on your behalf is secure it doesn’t really warrant a reason to decode your token multiple times rather than simply passing your access permission claims.
We do still pass your jwt between isolated “products” where your access request doesn’t pass through dapr, but rather back through our central access gateway and then into the other “product”. A product is basically a collection of related services which are restricted to be a business component. Like a range of services which handles our solar plants, and another business component which handles our investment portfolios, and so on.
How is this better than JWT if we have 30 microservices called from front-end?
My understanding was that this is the ENTIRE benefit of JWTs (over plain session token): they allow you go from a allowlist to a blocklist, which is more efficient at really large scales because you only have to store expired sessions (until their time-limit expires) rather than every session).
And if you're not doing this then there's no point in using JWTs (which will be the case for most people).
Are there any other benefits I'm missing?
I don’t know of any companies that even do this. As far as I know, most use cases store nothing, except for of course the client storing the response.
Actual current example: Small company acquired another with a complementary product, but on an entirely different tech-stack and cloud-hosting.
Product owners want customers logged into Service X to be able to follow a link to Service Y and not log in again, and vice-versa.
100% this. I am tired of you don't need microservices, you don't need JWT, you don't need Kubernetes, you don't need ElasticSearch, you don't need IAM, you don't need Redis, you don't need Mongo and everything should stay in one SQL database.
Things are being used not because they exist, because people want to be fancy or because they don't have something better to do. Things are being used because they solve problems and do so with least effort possible.
There's also "blinker-driven development" where people pick the solution based on their own personal set of hammers rather than, again, something more suitable.
(There's loads of these though - e.g. "optimisation-driven development" where the solution MUST GO BRRRRR even if the problem could be fixed by Frank typing in "Yes" once a week. "GOF-driven development" where everything has to rigidly fit into a GOF pattern regardless of whether it actually does. "Go-driven development" where everything has to be a interface and you end up reading a method called Validate which calls an interface method Validate which calls an interface method Validate which calls an interface method Validate and you wake up screaming every morning because why just wtf why please help me pleasehelp)
The whole codebase is riddled with the same kind of layering but we do now have guidance about doing stuff like that ("DON'T") and a plan to simplify some of the worst offenders (like the multi-layer `Validate` hell hole.)
I don't say what you describe doesn't happen, but it's my impression that most people try to adopt solutions that minimize costs and development time (which also translates to money). 99% of the time it's not "do the best thing to satisfy solve this problem" but it's "solve this problem as fast as possible, without adding additional costs and using as few developers as possible".
Agree with that but from my experience that's more like 20% of the time. The rest is the various kinds of bullshit development where people are padding resumes, having boss's pet hobby horse forced on them, latest shiny flimflam, etc.
(A decent chunk of that 30 years has been contracting and that tends to be at places with problems which might be biasing my sample set.)
Or people in this industry are geeks and curious and like to try new stuff and technologies just for fun?
That's the case for the majority of people I've worked with.
Sure but do that at home or on POCs. Not on production code.
At least on different schemas.
That and don't let one concern access data from another, or you'll have to coordinate schema changes between those different concerns.
God, how hard is SELECT * WHERE …, seriously.
You need to share a session across websites? Wow! Connect to the database holding the sessions.
Boring.
Standard solution is query a session table on from a single location, and once you actually start to need to trim request time, it’s not even the first place you look.
* everything is located physically close together (good luck reading from a DB table in Singapore from a service in Europe for every request)
* you have few enough client services that want to do this that the number of db connections does not become a problem (this is 100 by default for Postgres, you might need to tune this or deploy proxies, etc)
* high availability is already taken care of (you don't want that one DB server to bring down everything in case of a failure)
What you do to authenticate between the BFF (for lack of a better name) and other services is a different matter.
JWT makes it possible to distribute the same access token across multiple systems, but so do stateful tokens. The security implications when you're using JWT for this solution are much higher than with database tokens. Let's look at this for a moment:
JWT Security Issues:
- Inherent design issues (alg=none, algorithm confusion, weak ciphers like RSA)
- Cannot be revoked.
- Key rotation and distribution is necessary to keep token safe over a long period of time
- Claim parsing needs to be standardized and enforced correctly in all services (otherwise impersonation is possible)
Database Tokens Security Issues:
- Timing Attacks against B-Tree indexes [1]
- Giving direct access to the database to all microservices is risky
The security issues with databases are ridiculously easy to solve: To prevent timing attacks, you can just use a hash table index, split the token into a search-key part and a constant-time-compare part or add an HMAC to your token. To prevent direct access to the database by all your microservices, you just wrap it with an API the verifies tokens.
The JWT security issues are much harder to solve. To prevent misconfiguration or misuse and standardize the way claims are used across your organization, you probably need to write your own library or deploy an API gateway. To counter the lack of revocation support, you either need to use very short lived access tokens (so your refresh token DB will still get a lot of hits and you would still need to deal with all the scaling issues) or set up a distributed revocation service (not easy at all). Setting up seamless key rotation also requires additional infrastructure that is not part of the hundreds of JWT libraries out there.
It's really easy to get a JWT solution that just works and scales easily, but if you really care about security — especially if you care about security! — JWTs are not necessarily easier than stateful tokens. They're probably harder.
> Apart from that, Google and Facebook don't even use JWTs between the browser and backends after the initial login but actually do have some sort of distributed session concept last time I checked.
Last time I checked (which was today for Google), neither Google, nor Facebook is using JWT for their access or refresh tokens. The only place I saw JWT with the ID Token in their Open ID Connect Flow, and they can't really avoid that even if their wanted, since this is mandated by the spec.
Facebook and Google don't need JWT. Scaling and distributing a read-only token database to handle a large amount of traffic is easier — not harder! — for these companies. Stateless tokens can be useful for them in certain scenarios, but even then, if you're at Google or Facebook's scale, why would you opt for JWT over an in-house format that is smaller, faster and suffers from less vulnerabilities?
[1] https://www.usenix.org/legacy/event/woot07/tech/full_papers/...
Even with asymmetric tokens and a key distribution URL, you still have to make sure the clients periodically update their list of keys — this is not something you get built-in with every JWT library. And you still have to setup a mechanism for generating the keys and distributing them between the various instances of your auth server. This is not so hard nowadays with cloud KMS services, but setting up this solution on our own infra was quite painful.
> I agree that a lot can go wrong, but isn't that also true for home growing a distributed database tokens solution (I surely have seen some monsters in the wild). So can't the problems with both solutions be mitigated using some good libraries?
My point is not that a database solution is without its own issues. At my $DayJob we're also using a mix of stateful and stateless tokens (with distributed revocation lists) and we had to deal with issues with both of them. But stateful tokens on a database rarely suffer from security issues — the issues we had were always related to scalability and performance. The mitigations for these issues are also different: they almost always have to do with optimizing the infrastructure (scaling out the database, adding a cache) rather than using a library. In fact, when we use stateful tokens in a distributed scenario (and I'm sure this is true for almost everyone out there), all token handling is centralized in the auth service, so libraries are not strictly necessary. At most, client libraries would be very thin wrapper around HTTP API calls.
There is a huuuuuge gap between services handling 10k req/s and Google/Facebook.
I think one big upside with JWT that he doesn't mention is that if you have some services geographically distributed, then having decentralized auth with JWTs is quite nice without having to geographically distribute you auth backend system.
So, yes, if you have a monolith or services colocated, or have some kind of monolothic API layer, then no, perhaps JWT does not make sense. But for a lot of distributed services, having JWTs makes perfect sense.
And you don't have to introduce JWT revocation for logout, if you have short token expirations, you can accept the risk of token leakage. If the token is valid for like 30 seconds or 1 minute, you would probably never be able to notice that a token has been leaked anyway.
There is no reason you can’t keep a list of valid “session” identifiers and check the JWT is valid against that as part of verification. Then the only state you need to store server-side is the identifier. You get the exact same benefits of server-based session stores without needing to store the entire session on the server - just the identifier.
You don't need to be Facebook or Google to have more than one service in your infrastructure that needs to authenticate a user's existing session without forcing the user to log in again. Sharing the session across multiple services is its own distributed systems problem with numerous security implications to be aware of and bearer tokens might be a good alternative.
If all you have is a single monolith web app that is the identity provider, makes all authentication decisions etc then yes, you don't need JWTs probably. There is a huge gap between that and being Google/Facebook.
Apart from that, Google and Facebook don't even use JWTs between the browser and backends after the initial login but actually do have some sort of distributed session concept last time I checked.