Should I use JWTs for authentication tokens?
blog.ploetzli.ch
blog.ploetzli.ch
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.
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.
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 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.
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.
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?
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.
Back in the day we used memcached for our primary store for all sorts of ephemeral things. Including user sessions.
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.
How is this better than JWT if we have 30 microservices called from front-end?
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)
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.
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.
What you do to authenticate between the BFF (for lack of a better name) and other services is a different matter.
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.
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.
This post doesn't seem to address microservice architectures at all? For me, this is the primary reason to use JWT's -- so you can pass authentication ("claims", or whatever you want to call them) through your chain of microservice service-to-service calls. If you don't have microservices then there's much less reason to use JWT's.
I'm not saying the article is a strawman exactly, but it does seem to miss the primary use case of JWT's. At least, the way I've used them in anger.
Also, the "JWT's can be insecure if you use the wrong library or configure them incorrectly" argument, while having some points, seems to me more of an argument that you should really do due diligence on any libraries you use for security. The better JWT libraries are not insecure by default.
I wouldn't use JWT's if I were making a monolith, but there are lots of companies who (for better or worse) use microservices.
This is a misconception about so called zero trust. You can't "just" pass the same token to someone else. They can use it to impersonate or misuse the token later. While you are going to say that "my microservices will not impersonate users because to each other, they are all trusted," you have run directly into the difference between trusted and zero trust.
This is why JWTs are used in OIDC (e.g. “Sign-in with Google”: any website can use it, and it doesn’t make Google’s own security weaker.
I’ll concede that small, but important, details like these are not readily understood by those following some tutorial off some coding-camp content-farm (or worse: using a shared-secret for signing tokens instead of asymmetric cryptography, ugh) - and that’s also where we see the vulnerabilities. OAuth2+OIDC is very hard to grok.
What’s so terrible about that? Several security engineers I trust favor designs with symmetric crypto, e.g. Fly.io https://fly.io/blog/macaroons-escalated-quickly/ and Facebook https://eprint.iacr.org/2018/413.pdf
My interpretation is that the audience and scope claims, as other features like nonce, are in place to prevent tokens from being intercepted and misused, not to facilitate passing tokens around.
[1] https://salt.security/blog/oh-auth-abusing-oauth-to-take-ove...
Put another way, JWTs used as bearer tokens have vulnerable to intra-audience replay attacks.
While this is true for many zero trust architectures, but you don't have to build zero trust architectures this way. Simply have the token commit to a public key of a signing key held by the identity, then you can do Proof-of-Possession and remove these replay attacks. This is the direction zero trust is headed. For instance AWS is slowly moving toward this with sigV4A. Most zero trust solutions aren't there yet.
It's right in the name.
Anyway, zero trust architecture are wildly overrated and used in way more places than they should. But the entire thread is correct in that you can't build them with bearer tokens.
1. The client that holds the token can't use an HSM or SSM to protect the token because they need to transmit it. Thus a compromise of the client via an XSS or Malware, results in the token leaking.
2. The server that receives the token, might be compromised and they can replay the token to other servers or leak it accidentally e.g., with a log file or to an analytics service.
Both of these problems go away if you uses OpenPubkey or Verifiable Credentials with JWTs. The JWT is now a public value, and the client holds a signing key.
1. The client can protect the signing key with an HSM or SSM (modern web browsers grant javascript access to a SSM).
2. The server only receives the JWT (now a public value) and a signature specific to that server. They don't have any secrets to protect.
> But the entire thread is correct in that you can't build them with bearer tokens.
You can and people do, but it is far better to use proof of possession JWTs than bearer JWTs. Even better to use JWS instead of JWTs so you can make use of multiple JWS signers (a JWT is a type of JWS, but a JWS with more than one signer can not be a JWT).
Really the ergonomics are why this hasn't been adopted more readily. I wonder why it's possible to have OpenTelemetry inject a header into thousands of different APIs and services for dozens of programming languages, more or less flawlessly. But if I wanted to do this at process boundaries, and the content of my header was the result of a stateless function of the current value of the header (aka token exchange + destination service): you are shit out of luck. Ultimately platform providers like Google, Apple and Meta lose their power when people do this, so I feel like the most sophisticated and cranky agitators are more or less right that the user experience is subordinate to the S&P top 10's bottom line, not real security concerns.
> I don't really want Google or Apple or whoever has, for idiosyncratic reasons, possession of a private key, to sign every request I make to Google Calendar, because they will inevitably revoke it sooner for obnoxious business reasons than any good security reason.
Can you provide more context on this? I would assume asymmetric signing keys are less likely to be revoked than say an HMAC key since an HMAC key must be securely stored at both the client and server whereas you can just put a asymmetric signing key in an HSM at the client and be done with it.
Fair point. This post assumes a single database which opaque tokens can be mapped to. That said, a lot of smaller webapps are and should be monoliths.
Is it about battlefield and such?
My understanding: To use something "for real" on an actual project, not just toy around with it. (What you use can be good or bad, expression doesn't say)
Or just, you know, service architectures. Most microservice architectures I've seen go way too far down the route of breaking up services and their infrastructure to an impractical level. But all you need in order to make JWTs really useful is two federated services. This happens all the time, often in the course of some partnership, acquisition, or just an organizational structure meant to decouple 2+ teams from each other.
Based on their mention of a single framework connecting to a single database, OP seems to have never moved past the point of developing a single service. Which is fine! It makes things simpler for sure, and you can get very far with that. But they are then dispensing advice about things they don't seem to know much about.
I disagree with the whole "you're not Google/FB"/"over arbitary RPS" logic though. If the design makes sense then it makes sense, end of story. Just understand it.. lol
Not needing a DB connection to verify means you don't need to plumb a DB credentials or identity based auth into your service - simple.
Being able to decode it to see its contents really aids debugging, you don't need to look in the DB - simple.
If you have a lot of individual services which share the same auth system, you can manage logins into multiple apps and API's really easily.
That article seems to dislike JWT's, but they're just a tool. You can use them in a simple way that's good enough for you, or you can overengineer a JWT based authentication mechanism, in which case they're terrible. Whether or not to use them doesn't really depend on their nature, but rather, your approach.
It's not difficult to implement JWT's, the concept is simple, however, with authentication code, the devil is in the details, and that's true for any approach, whether it's JWT's, or opaque API tokens, whatever. There are many, many ways to make a mistake which allows a bypass. Simple concepts can have complex implementations. A JWT is simply a bit of JSON that's been signed by someone that you trust. There are many ways to get that wrong!
Convenience, when it comes to auth, is also usually the best path, and you need to be careful to use well known and well tested libraries.
"You don't need all of that!" sure but you probably already have it somewhere in your codebase and it's pretty universal. You also probably don't utilize every feature of http itself, that isn't a cogent argument against using http.
JWTs are supported by a large number of tools, libraries, middleware appliances, etc; there's a huge ecosystem out there to support it.
You also might delegate auth to a third party like Auth0 or FusionAuth so that you don't handle any PII, because all of the PII is handled by a vendor, and you only store application-specific data.
"You want to implement logout" means a few things; in most apps you just ... have the client forget the token and you go about your day and it's fine. "but what about if a nefarious actor stole the token!!!" you might say, but hand-rolled session tokens have the same problem.
"You want to turn off access for all users" is something you can do in http middleware; e.g., I have used middleware that do things like "only allow through requests that have jwts with the 'admin' role in their claims because we have turned off the system from users for downtime", and that works fine. (specifically I wrote a traefik plugin to do this in an afternoon).
"You want to ban a single specific user really quickly" is a thing JWT won't do out of box.
If you read this and think "this doesn't apply as long as I have to use JWTs for some service I rely on, anyways", you missed its point.
I think there's a cyclic thing that's been happening for years where people in the security community like to talk about how bad jwt is, but then not produce anything that meets application developers needs in a meaningful way. I spent years avoiding jwt and ultimately found avoiding jwts wasn't actually a good use of my time.
I see some strong arguments for standardizing on a single one.
1) You may not want your application servers having direct access to your auth service or auth database. You may not have the resources to control employee access to sensitive data when it’s shared across services. You may want to spend limited resources for security audits on the systems that contain the most sensitive data, and having them separated from everything else is helpful.
2) Depending on what 3rd party services you use it may not even be practical to have connectivity between auth and other services, and if you do, the latency may be bad enough that you wouldn’t want it to be blocking every request. This is especially compelling for hybrid environments, e.g. the 20 year old database in a colo with your user data, and a new service being built for you by consultants on a PaaS.
3) People act like revocation is such a nightmare, but you only need the auth service to sign invalidation tokens to be passed to the client-facing services, and those services only need to retain them for the max TTL of the auth tokens, after which they can be evicted. Yes, that’s something that could be motivated by having a massive environment like Google, it could also just be a way to keep costs down when you’re paying by the byte of storage or by the outbound request on some cloud. You could try to make the argument that storing invalidation data is just as bad as storing session data, but the key questions are “Where?” and “For how long?”, and then in some circumstances it breaks down very quickly.
4) You may not want sensitive user data stored in the jurisdictions where you want to host your apps. That’s not a big company problem, it’s dependent on the nature of the services you provide.
I keep seeing the idea that JWT was designed for Google or Facebook scale being repeated over and over, but the reality is that neither company uses it. Last time I checked, both used rather short access and refresh tokens and it appears that at least the refresh token (if not both) is stateful.
Implementing stateful tokens on a global scale and sharing them across hundreds of service is HARD, but it's easier when you are Google or Facebook, and you've got enough resources to throw at this trouble.
And if you do need to implement a stateful token, you've got every reason to choose your own format. Your applications are using your own authentication libraries and infrastructure (e.g. API gateways), so you don't have to worry about complicating their life with a non-standard format. The upside for using your own stateless format is that you avoid all the design issues with JWT (alg=none, algorithm confusion, questionable support for outdated algorithms from the 1970s) and you can design a far more compact format that takes a fraction of the size of JWT[1].
There's a reason JWT got popular with scrappy startups, enterprises hobby projects and Udemy/Medium tutorial authors: they're very easy to spin up and library support is everywhere. I don't mean to say JWT is the right choice for any of these uses — it probably isn't. But it's the easy choice, the worse-is-better solution. The worse solution needs only be better in one respect to win: it should be easier to implement, copy and spread.
In the end of the day, JWT is not a good solution for either Google-scale companies or small startups. But it's the small startups that usually lack the resources and awareness to adopt another solution.
[1] https://fly.io/blog/api-tokens-a-tedious-survey/#protobuf
Not only is this true, but most actual deployments of JWTs just have you swap a JWT (ID Token) for a opaque session token.
That said, I really like having a JWT signed by an IDP which states the user's identity because if designed correctly you only need to trust one party IDP. For instance Google (the IDP) is the ideal party to identify a gmail email address since you already have to trust them for this. I created OpenPubkey to leverage JWTs, while minimizing and in some cases removing trust.
OpenPubkey[0, 1] let's you turn JWTs bearer tokens into certificates. This lets you use digital signatures with ephemeral secrets.
[0]: https://github.com/openpubkey/openpubkey [1]: https://eprint.iacr.org/2023/296
This looks really awesome, thanks for sharing.
To be able to sign a message you not only need the ID token but also the private/signing key, and the corresponding public key is bound to the ID token (using the nonce field).
Thus you can prove that not only did Google say you are you, but you possess the signing key associated with the ID token that says so. Thus I can be sure someone else didn't just steal your token in flight or from a log file for example.
JWT bearer tokens use a signature to issue an identity, but that don't include the public key of that identity. The issuer has a public key, but the issuee does not.
There are plenty of JWTs that are certificates:
* proof-of-possession JWTs,
* self-issued JWTs, etc...
1. It standardizes your auth system. While sessions auth is mostly implemented in the same way across systems, learning oauth and oidc gives you a standard across the industry.
2. Jwts give an easy path to make “front end” applications and api authentication work in the same way. This in theory reduces your security surface area as all of your authnz code can be shared across your offerings.
If a short session time isnt good enough, you can use a simple key store to check for revoked tokens. Youll be hitting a db but its somewhat better since its just a very small db of revoked tokens.
Its hard for me to imagine though with like a 30 min or even few hour long token, under what circumstances you'd actually revoke tokens. If your db got leaked, you can rotate the key and invalidate all tokens. Otherwise, itd have to be something like you have some post login fraud detection in place. Cause jwt or not, if a user just signed in and a hacker got their auth token, what are you going to do? Sure you need to check the db to revoke it, but the problem is how would you know the tokens been compromised?
That keeps your application DB free for application load, while keeping your identity validation logic nice and snappy.
Of course, adding infrastructure may be intimidating, but most applications that face any real load are going to be using redis or something similar anyway at some place in the stack.
I don't understand this argument against JWTs; if instantaneous session revocation is important for your use-case, versus JWTs more typical 5 minute or 60 minute expiration, there is nothing about JWTs which makes them poor candidates for going to the database as you would a session token. And, you get all the other benefits.
One example where this can matter: I've seen JWTs used in defense-in-depth scenarios where you've got an API gateway that does the initial JWT validation, including a round-trip to the database to check for revocation, but then had microservices behind the gateway only check the signature. Traditional session keys would require a database roundtrip for every validation, which could number in the dozens for a single API request.
You would be required to keep a Map<UserId, Timestamp> in memory, potentially with TTL. Most systems can handle this easily for their expected user load. If not, you should have the engineering capacity to figure it out ;)
Logout button sets reset_time to now, as does revoking tokens. This would only allow you to revoke all tokens for one user at the same time, but this tends to be fine, since JWTs should be short-lived anyway and apps should deal with the expectation of them being expired/revoked.
I have a template that's backed by terraform and the authentication client is in lambda so the whole thing is serverless, self-contained and practically free. So I just run "terraform apply" and I have scalable auth for my new service.
https://github.com/alshdavid/template-cognito (only 1 dependency on AWS, everything else is stdlib)
If any service I create is lucky enough to break out of the free-tier and cost is an issue, then I can just move to another OAuth2/OIDC provider. The auth mechanism Cognito uses is just a specification meaning I am not coupled to any one service provider (though the user accounts themselves are). Cognito, Auth0, IdentifyServer, or whatever - I can migrate if cost becomes a problem.
The big issue with JWTs are that, if lost, they give permissions to attackers without revocability.
For this reason, I keep auth-tokens short lived and refresh them often. Refresh-tokens are revocable and live for a few days. This means that a lost auth-token is only harmful for a few minutes while a lost refresh token is only harmful until revoked or expired.
Tokens are stored as path-specific http-only cookies so the only vector for attack is if a user physically opens devtools and gives an attacker the token - or if the attacker has access to the computer (physically or via a malicious terminal script).
High risk operations (e.g. delete account, delete content, anything high risk) requires "step-up" authentication - so a user is asked to re-authenticate in those cases.
Overall, when you consider that rolling your own authentication comes with the liability associated with holding user data (companies must announce a breach to users, etc) - if a service provider like Cognito is compromised, you won't be liable or the only one affected.
JWTs have security concerns, but on balance, when used with third party provider, a sensible configuration and considering the risk of rolling your own - they are fine.
> absolutely no one who is not Google/Facebook needs to put up with the ensuing tradeoffs. If you process less than 10k requests per second, you’re not Google nor are you Facebook
What's the magic property that flips when you pass 10K requests per second? Are we sure it's at 10K requests per second, not 8K? or 5K? In general, at that kind of scale I'd think JWTs would become less appealing - AWS operates on IAM for example.
And why are Google and Facebook the best examples of companies who are operating at scale? There are different kinds of scale than just 'ad auctions per second'. I would imagine the access management concerns of, say, JP Morgan Chase are at least as complex and challenging to scale as those of Facebook.
Making authentication only require a signature verification at the edge (JWT) vs authentication middleware that needs to do a DB read (opaque), can be a life saver even if you have 10 requests a second most of the time.
Q: What about the endpoint that's issuing the tokens?
There are definitely arguments to me made against ridiculous over-engineering, modern web dev has taken the problems of 1% of engineers and made them problems for 100% of engineers, but I think this is a bit of a silly one to focus on
A centralized database for sessions, and we can extend/revoke any token anytime.
I'd argue that people who need to avoid hitting their database on every request outnumber people who need sub-minute revocation
More so, A revocation list on something like Redis can expire with a token and a lot less expensive than an rdbms lookup with joins.
Not too mention, why add extra load with extra DB calls in the first place?
You don't have to be Google scale to want to separate logins from the main service. And that's just for starters.
The real thing to push back on is the logout requirement. Everyone pretends they need this, when what almost everyone should do is just mandate appropriately short token lifetimes and revoke refresh tokens as needed.
That’s a logout requirement?
Blocking refresh in the authorization server is trivial, but trying to implement the same on access tokens in the resource server at the point of use breaks the entire security model of JWT. It's unreliable, because now every resource server has to take on partial responsibility for authorization which multiplies opportunities for mistakes. As the OP points out, you need to keep track of some sort of block list and lose out on many of the benefits of JWT (i.e. a resource server being able to rely fully on claims in a signed token before allowing an action).
When people show up with this kind of requirement, in my experience, it is often because they foolishly configured a client with a very long expiration on access tokens (e.g. ~months/years instead of ~minutes/hours). This creates a problem when some aspect of a user's access needs to change (e.g. disgruntled employee was fired, customer didn't pay their bill, etc). You can address this more easily by pairing a short access token lifetime with a long refresh token lifetime.
Additionally, when using asymmetric validation, you can rely on JWTs as licenses: Your software can restrict offline features based on a locally stored token, simply by checking that the JWT was signed by the authority. In tandem with the ability to store metadata, your app (with code held by the untrusted user) can use the token to determine the user's license features without requiring an always on connection. (Obviously patching out the license checks is another matter)
These features can be layered on top of opaque tokens, but since a JWT has all the benefits of an opaque token (store the opaque token as a claim just like the rest of the metadata), it's actually a complete package that does it all without needing to roll it yourself.
Revocation is just hard. Even PKI's CRLs/OCSP don't have a subscription mechanism by which an end-point can get revocations of some small set of certificates. Even a public feed of revocations could be problematic if you're going to terminate employees because the timing has to be perfect: too early and you're tipping off employees, too late and you're allowing disgruntled employees run malware on your systems and network. In practice being late to revoke access is generally accepted as acceptable as long as it's not too late.
I don't think I would start with JWT if I did this again.
Yes, query the DB or some sort of storage every time. It sounded so clean and nice and fast to just check JWTs without any network calls. But it ended up very messy and complicated. Might still be worth it in some cases, of course, but I would start my next project with random sessions stored in a db or redis or memcache or .. something :)
You can actually do crazy stuff with your sessions as well, to avoid a normal db lookup. But in practice all services I have worked on would/did not suffer noticeably for a fast DB lookup.
I've experienced more than once, issues where the auth service has bugs and the logged out session is still valid for a long time. Or an attacker that figured out the micro services blueprint and now had authed access to the entire network.
Jwt is still useful between services, however the front end can do just fine with a session id that can be easily revoked.
In my projects, we have used encrypted JWTs and it seems to me a fine solution. Log out can be implemented in a user facing client by deleting the JWT and refresh token. Given a short enough expiration time, this is sufficient for most use cases involving user facing applications. Isn’t it? Generally, at least in the domains of the applications I’ve worked on, users only intend to log out of their current application when logging out. Meaning if they are signed in on their phone and on their desktop browser, when they sign out in the browser they don’t intend to also log out of their phone application.
The only downside I see is that if you want to log out of all sessions it is impossible to implement without maintaining session state in the server.
JWT's security -and that of all bearer token systems- depends utterly on the use of TLS.
The security of JWT used correctly is comparable to that of Kerberos or PKI. There are some things to look out for, like that it's way way too easy to write implementations that fail open (e.g., you don't recognized a signature algorithm so you don't validate the signature but also fail to fail), but other than that there's nothing particularly bad about JWT.
> Log out can be implemented in a user facing client by deleting the JWT and refresh token.
Logout has to be implemented either by letting time pass w/o re-authenticating (i.e., let the session expire) or telling the server that you're logging out. You can login again, even with the same token as before (IFF it's not expired yet). There is no reason to have to "burn" a token, and that would add a significant amount of complexity akin to revocation.
> The only downside I see is that if you want to log out of all sessions it is impossible to implement without maintaining session state in the server.
Sessions imply session state. What you probably meant is that if you want to revoke access then you need a revocation system, and that effectively bolts on extra state on your servers (and a pub/sub system). Generally no one wants that headache.
No they don’t. JWTs can be used with third party services that should never see a refresh token. Those are the requests that should be presumed vulnerable.
Suppose you use Azure. You may have one or more app registrations with defined roles as well as apis exposed within the Azure config. It's convenient to acquire an access token that can be sent to various apis, each of which can accept the token without having to worry about session state or really anything to do with authentication other than validating the token's legitimacy. If I wanted to roll my own security, maybe JWT isn't how I would choose to do it, but I definitely don't want to do that.
For the record, I use JWTs, I have hardly any scale at all. I have none of the requirements that would hit the database on each request. It works just fine and I didn't need to consult an article to tell me what to think because, and this is the crazy part I know, I'm able to break apart my business requirements and decide for myself what is needed (or not). JWTs work just fine in my case. YMMV.
The security arch is mainly in web/auth and web/interceptor packages if anyone is interested in learning from the code. It uses connectrpc, which has a nice interceptor arch.
Happy to share Vera’s thesis report if anyone is interested…
Please share her thesis. I'm interested.
Everyone wants to be big. You can either frontload those problems, and risk drowning in irrelevant complexity, or defer them. The problem with deferring is that switching lanes appears like it will be expensive and time consuming.
The secret is that if you solve actual problems, rather than dreams and future hopes, the answers are almost always obvious. The work may be painful and people may be angry, but the rationale will be clear.
You need to be able to turn away malicious users as fast as possible. If you take the time to check a database first, that's a precious resource they can consume.
"Add a cache!", you might say? What if they use random client id and client secret for every request, how do you cache against that?
At some point the attacker either gives us or just switches to the brute force of flooding the pipes with so much traffic that the AS doing the filtering goes down. At that point it is now someone else's problem. They might start de-peering the ASes generating the traffic.
Even at a new company I don’t think I’d ever want to go back to stateful sessions. I’m not close to Google or Amazon scale, and managing state was significantly harder than dealing with JWTs.
Secondly, you don't have to hit the database on every request. Unless you have really strict security requirements, you can have a short expiration time on the jwt with a refresh mechanism, and then you only have to check the database say once every 5 minutes.
Related to the above point, the database you check for the "session" token isn't necessarily the same as the one used for other data used in the request, even if you are much smaller than Google or Facebook. It might not even ve the same type of database.
Finally, even if it makes sense to use a "traditional" session cookie for brower sessions, that probably doesn't make sense for an external API, where the client may not have persistent cookies at all, and there may not really be a concept of a session.
So as was mentioned in another comment, I think the answer to the title question is a solid "it depends".
I would like to see an open-source project from this author that uses his proposed solution.
So, that may be why we don’t see more “opaque session token” standards/libraries out there as an alternative to JWTs.
But if you want an existing example, Devise for Rails [1] has been around a while.
If so, most robust session-based authentication libraries can already store this data for you (e.g. Devise's `trackable` module). Actually locking down the session so it can't be used by different IPs/UAs would require custom code on the server, whether you're using JWTs or session tokens.
So JWTs provide zero advantage here, unless I'm misunderstanding something.
Whenever I see such a simple answer to a complex question, I know it’s probably an oversimplification.
The real answer is a solid “it depends on what you want to achieve.”
Say you handle invalidation by maintaining a cached table of revoked tokens. Is this table larger or smaller than the table of all users?
Perhaps you would like to embed some RBAC info in the token. Is validating both the token and it’s absence from your revocation table more efficient than looking up all of the other information?
Perhaps you need to do this on a distributed basis. Is the overhead of maintaining such a table more or less than making all the DB calls and creating a central choke point in your architecture?
The JWT always stores facts about the principal (aka who or what is doing something), and those don't really make sense to revoke or whatever anyway. Stuff that will never change over time. JWTs can optionally store something like a `role` or similar fact that may change over time, specific to your application. Those facts can be used to decide what you can do in your application, that's authorization. We could talk about when and how that should be done, but it would be too nuanced for these evergreen JWT blogposts.
2. JWT doesn't have to contain user details, it can be a simple reference token used for introspection [1]. This approach removes problems with invalidation and logout since identity service should store invalidated tokens.
3. JWT can contain refresh token id (or hash) and check for invalidated refresh tokens in (distributed) cache. Cache entry should not be long lived, and refresh token shouldn't be long lived if you are using proper refresh token rotation with family invalidation in case of a leak.
That being said cookies are still simplest and safest bet for simple monolith applications.
The meta data in the JWT is sort of a short cut to let the front-end make assumptions, but it has no bearing on the actual capabilities. Only a valid Bearer token can determine if a call is secure (authenticated) and has the correct permissions (authorized).
So, you don't need a JWT, but without it, you're still going to need a way to send mundane meta data back to the front-end. This used to be (and still can be) a separate call for "config" or "permissions" data, but why bother. Just create claims in your identity server, mark your API's with those claims and token validation, and you're in great shape.
But if you just think of this author’s narrow view of what system designs look like, then skip the JWT.
In a typical Windows/AD or Unix installation you might have Kerberos (definitely in the Windows/AD case, possibly in the Unix case) and a directory (definitely in both cases, though in the Unix case it might be purely local), and you use the authentication system (Kerberos, say) in such a way that it allows you to elide some directory lookups on Windows/AD or not at all on Unix. Either way you get a worst-case latency for revocation of the user's service ticket's lifetime (Windows) or whatever you do to stop locked user processes (Unix). Well, that's the theory, but in practice many SSHv2/whatever implementations do not force session end at ticket expiration. So Windows/AD uses group policy for revocation. On the Unix side... we don't really have a standard solution, but essentially one can build a daemon that periodically looks for and kills processes running as now-locked users.
(For SSHv2, if you build re-authentication into a KEX, like GSS-KEX does, then you can force re-authentication and session end when authentication expires. In practice on Unix systems it's not really possible to elide a directory, so this is not necessary.)
Now for HTTPS apps it's a different story. You can use cookie expiration and force re-authentication so that revocation latency is bearer token lifetime, then keep bearer token lifetimes short. If you don't control the token issuer then you can still impose a shorter cookie lifetime in your app and demand a fresh token when you force re-authentication.
In any case, one does not need a revoked token/ticket/whatever list!
But it is true that bearer tokens don't completely elide the need for a directory for things like SSH logins onto hosts. But they do for HTTPS apps.
So I think TFA is just wrong.
In a hyperscaler situation things are different, but we should avoid treating that as the norm.
If you have a monolith anyways then yes, why use a distributed systems solution like JWT? Completely agree.
But if you already have an auth service, making it optional for the majority of requests is a distributed systems win. Even if you need to implement forced logout or some other features which require hitting the auth database, they can be optional requests. If the auth service is available, you get better security, otherwise the services can decide whether to continue or not.
This is better than your entire app going down or slowing down with the auth service. Though the refresh token bit is still a challenge, it’s a smaller one than a hard dependency on the auth service on every request.
Again, if your auth service is just a component in your monolith, the author is completely right. It’s context-specific.
It’s a well-documented standard and we’ve never had an issue with them since launching.
The supposed drawback that sessions must live for X minutes, is just not a problem in practice.
The author of this essay even gave us a few example links[1][2][3]. Most users of JWT didn't go through a security incident, but a large enough percentage did. Far larger than you get with any other method.
> It’s a well-documented standard
I beg to differ at this point. Yes, the base standard itself is well-documented, but a safe way to use it is not. The JWS and JWE specify a great many algorithms without recommending which one of them is safest to use. Some of these algorithms (e.g. most types of RSA in the spec, AES-GCM with KW) are unsafe without extra precaution. The also doesn't mandate any claim (including the "exp" claim!) and does not mandate setting up key ids (which are necessary for key rotation).
If you need to encrypt your token contents the standard also doesn't help you: It just gives examples of how you'd use an unauthenticated encrypted JWT[4] or a "sign-then-encrypt" JWT-in-JWE with RSAES-PKCS1-v1_5[5]. I cannot stress this enough, but BOTH examples in the standards are very obviously insecure. The first example has no signature or authentication at all (i.e. everybody can forge this "encrypted JWT"), while the second example uses a naive sign-then-encrypt scheme with the worst protocol for this type of scheme: RSA PKCS #1 v1.5, which is vulnerable to Bleichenbacher's attack[6].
If you want to use JWT securely (assuming you're ok with 5 minutes access tokens that cannot be revoked and you'll still be maintaining a database for refresh tokens), here is what you need to do to use it safely:
1. Use the HS256 algorithm for monoliths or services behind a single proxy or API pgateway.
2. If you want to distribute authentication across multiple services without making it possible for the various services to generate tokens, use Ed25519 (as per RFC 8037). Unfortunately, this is not supported by many libraries out there. ES256 is not terribly insecure for signing tokens, but there are enough caveats (such as its behavior when the RNG fails) that scare me.
3. Encrypted tokens should be avoided. If you really need them, they can only be used symmetricly (i.e. same way you'd use HS256). They should be done by embedding a JWE payload inside a JWS. The JWE should use alg="A256KW" and enc="AES_256_CBC_HMAC_SHA_512".
4. "exp", "iat" and "jti" claims are mandatory. "jti" should include a 64 byte random value encoded as base64url.
5. "sub", "aud" and "iss" claims are mandatory and should be verified.
6. A "scope" claim containing the tokens scope should be included and verified.
7. A "clid" or "azp" claim containing the Client ID should be included and verified, to identify the client.
8. All keys MUST have a "kid" specified, to allow rotation.
Or you could just use PASETO v4.local/v4.public and get most of that work done within the library itself.
[1] https://nvd.nist.gov/vuln/detail/CVE-2024-22513
[2] https://www.howmanydayssinceajwtalgnonevuln.com/
[3] https://auth0.com/blog/critical-vulnerabilities-in-json-web-...
[4] https://datatracker.ietf.org/doc/html/rfc7519#appendix-A.1
[5] https://datatracker.ietf.org/doc/html/rfc7519#appendix-A.2
[6] https://datatracker.ietf.org/doc/html/rfc8017#section-7.2
It was a rush outline article with citations.
Large binning, salting of hash, and revocatable are my criteria.
Some toolkits that went out the window firstly are:
* auth0,
* Fusion auth, and
* Gluu.
So, some of the basic criteria are:
* User and password login instead of plain HTTP session cookies.
* HTTP-only over TLS v1.2+ (secured HTTP, HTTPS)
* ECDSA 1K or better
* SameSite [1]
* __Host prefix [1]
* preload HTTP Strict-Transport-Security header line [2]
* Bearer token supplied by API clients
* Don’t listen on port 80… like ever. Or revoke token if over non-port 443.
* DO NOT use JWT [3]
* DO NOT use CORS [4]
Hope the citations help more.
JWT, not recommended, IMHO.
https://egbert.net/blog/articles/authentication-for-api.html
For users/people/apps, I usually rely on session-based authentication. Sometimes I need light RBAC at this layer too (users, teams, admins, etc).
I don't implement JWT because I fancy big data terascale technologies. I do it because it's mostly stateless, meaning it's very easy to mock locally, and easy to deploy.
> 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.
No, you just remove the JWT from the local storage. Sure, the user could then relogin if he copied it, but if it's on his own will, why not. And if he got his JWT stolen before logout, bah anyway any token could have been stolen that way, whatever the tech.
> 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.
Right but that contradicts the whole premise of the article, being that you probably don't need fancy features. 99.9% of websites don't need to ban users _instantly_. If you have a fair JWT expiration, it's usually OK.
I'd argue for the exact opposite of the article. If you want dead simple Auth, without fancy tech, just use JWT.
The thing is, with the good old "token in the database" method, logging out means deleting the token from the database so no, you can’t reuse a stolen token after a logout.
It's a slightly different story with a "logout all sessions" button where the user might press it from a trusted device, but that's a different functionality than a logout button. There you would need revocation, but if you (like many websites) don't actually support this functionality you really don't even need to support a logout functionality more complex than just asking the client nicely to please delete the token.
You'd have to store something somewhere, so you end up losing the benefits of stateless authentication.
Implementing it securely, however, is far from dead simple.
https://scottarc.blog/2023/09/06/how-to-write-a-secure-jwt-l...
Yes, and why is that a problem? It is the best of both worlds as the verification of access token is standardized and fast while refresh token could be used at the first call of the session. Yes, it could happen that the user is logged in for 5 more minutes if the user is in middle of session, but it's really such a edge case which most companies don't need to worry about.
If for some unknown reason the recovered client transaction counter losses its expected position, than you know someone has tampered with the session, and or stolen your clients session token.
Donate to the FOSS project of your choice if you find this useful. =3
How is no one thinking of a mere pub-sub topic? Set the TTL on the topic to whatever your max JWT TTL is, make your applications subscribe to the beginning of the topic upon startup, problem solved.
You need to load up the certificates from configuration to verify the signatures anyways, it doesn’t cost any more to load up a Kafka consumer writing to a tiny map.
But this is way overkill for anybody that isn't FAANG, and it's probably overkill for most of FAANG too. On normal usage, it's standard to keep the revocation filter centralized at the same place than handles renewals and the first authentication. This is already overkill for most people, but it's what comes pre-packaged.
If you have a pub sub cluster that you push revocation details into, and running servers subscribe to that feed and then track a rolling list of tokens that would otherwise still be active but have been revoked, you are effectively storing a revocation database on every edge node.
No if the token can become invalid due to other reasons because lets say the user deletes the token because it got leaked. But since you have no way of invalidating the token other than changing the encryption key, you can't selectively invalidate that one token.
Why would I, I can just issue a jwt and move on, I don't need to store anything apart from a key to check it with.
And using jwts with nodejs (which I use most frequently) is a super simple and easy npm install, it's almost zero effort to use a jwt.
However, too many times they're implemented incorrectly or without forethought. I've seen a few teams who used the bearer token, set the age really high, and never bothered to implement a refresh mechanism.
Fast forward a few months and someone says it is not possible to log someone out of our service.
I believe the issues he is describing is more OAuth2 rather than JWT.
I feel like this subject comes up every couple of months
with another iteration of why people think JWTs are bad.
The regularity which with this subject comes up makes me think of how Triplebyte used to pay off bloggers in the day to write posts with the title “hiring is broken”. I don’t have the proof but I am picking up a bad smell here.Gotta be yet another of the vendors which is selling some authentication system that they host. I remember trying to promote an open source user management system in the early 2000s and nobody cared until they added killer feature “our service will get shut down when we get acquired” which somehow drew the pointy haired boss like a moth to a flame.
https://owasp.org/www-chapter-vancouver/assets/presentations...
I have no idea why there is so much gaslighting about them. People keep pointing to implementation complexity, yet there exist proven, heavily used implementations for almost every language and engine imaginable. That issue is an issue of the past.
It is doubly ironic that people try to point to 'complexity' as an argument against them when most of them are addicted to over-engineered software stacks... JWTs are amazingly simple when compared to most other concepts in this industry.
The aversion towards JWT is weird. I used to think it's probably because some people got burned badly due to past implementation flaws and still hold a grudge... But the sheer persistence of the any-JWT movement leads me to think there may be an agenda behind it.
How could anyone possibly claim that keeping track of sessions on the back end and passing around session IDs is superior? It's way more expensive on the database/datastore, adds latency, it's hard to scale, you often end up with stale sessions because it's not fail-safe (e.g. how to clean up previously active sessions when a worker crashes? Especially if you have multiple workers.). Session IDs are MUCH more work.
And I'm wondering if you can comment on the points raised in the article regarding how you handle logging out with JWTs? It seems you've got two options:
1. Don't implement logging out
2. Implement logging out by keeping your JWT expiration time short (which is probably good practice anyway) and using a refresh token to issue new JWTs periodically.
With the latter, you've gone right back to server side session storage. So JWTs are just an added (unnecessary, for most apps) layer of complexity on top.
Again, all of this was discussed in the article.
So to summarize, JWTs save you from having to store and maintain a session object. You just don't need one. A blacklist or ban list is a lot easier to manage than session data, only a small number of users will be blacklisted. You could also add an isBanned field to the user's account object itself. No need to maintain a separate 'session'.
For log out, you just make the client remove their JWT from wherever they have it stored; e.g. memory, localStorage or sessionStorage. Once all valid signed JWTs have been physically deleted from wherever they were held, the user is logged out.
Even if you consider edge cases; e.g. If an attacker managed to grab hold of a user's JWT just before the user logged out; it begs the question, how did the attacker manage to do that? An XSS vulnerability in your front end? Well, once resolved, you'd probably want to invalidate all your previously issued JWT tokens (for all users) by changing your auth signing key on the back end... Just to be safe right? It's kind of neat that you can invalidate all previously issued JWT just by changing the signing key. It's instant and doesn't cost any resources unlike deleting large numbers of session objects from a datastore.
I can't think of any situation where you'd urgently want to invalidate a single JWT/session that couldn't be handled better by adding a single isBanned field to an account object or via some kind of IP blacklist. If you're using sessions with session IDs and the user is banned, you'd want to add some kind of flag to their account object itself right? Not just the session... With JWTs, you just need this flag to exist in 1 place, not 2.
And issuing a new JWT every 10 minutes certainly seems akin to “setting and constantly refreshing expiries on the session keys” which you called out as being a drawback of server-side sessions in a different post.
JWTs are good for server to server communication (short lived at ~5 minutes).
Sessions are for clients (browsers, apps, anything controlled by a person) to communicate with a server/api.
Also in that case you aren't using JWTs for authentication, AzureAD is and you're integrating with it. And as I said, you're integrating with it via OIDC.
If you're using AzureAD for IAM, you're not "using JWT for authentication tokens", you're using AzureAD. This isn't what the article is about. The article is about building your own services that generate and process JWTs. And yeah, if you use an OIDC API you likely use an OIDC library instead of rolling your own. So as far as your own code is concerned the token is entirely opaque.
It's like asking what language should I use for programming a game? A common question, but of which the answer isn't a language choice. Instead it is that you approach the problem from the wrong direction.
The more useful question is what framework/service/library do I use for handling that, and then you use whatever it uses. Which most times means use whatever your web framework provides (as the blog author concluded).
The next question is what do you concretely hope to get from doing so and are there easier ways to get it (likely yes, and not some vague "but statles is better argument").
Then ask yourself how do you handle revocation? A question which is essential when using JWT and other stateless auth tokens (a answer of idk./I will think later about don't count. I don't can be a valid answer, but only very very rarely).
I think it's not an understatement to say that the huge majority of custom JWT usage falls under harmful premature optimization.
I wrote "custom" because sometimes you don't have a choice, e.g. you need to use OIDC for social login as the main form of AuthN & AuthZ and then already have some service (not just library) which fully handles OIDC/JWT including revocation for you (i.e. you don't validate the JWT stateless but ask the service every time). Through that approach (especially the later part) can have scaling limits but, eh, we are back at premature optimization ;)
https://www.rfc-editor.org/rfc/rfc7519
It can be signed with HMAC SHA-256 algorithm: {"typ":"JWT", "alg":"HS256"}
Ripping off JWT from surrounding context is a road to hell.
It's worth to study JWT in context of OIDC (OpenID Connect) IDP providers.
You will quickly bump into buzzwords like client (RP), server (OP), PKCE, Token Exchange, mTLS and all kind of Implicit. Hybrid flows.
My biggest regret I didn't go through JWT related RFCs and OpenID Connect specs earlier.
[1]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFl... 3.1. Authentication using the Authorization Code Flow
[2]: https://openid.net/specs/openid-connect-core-1_0.html#Implic... 3.2. Authentication using the Implicit Flow
[3]: https://openid.net/specs/openid-connect-core-1_0.html#Hybrid... 3.3. Authentication using the Hybrid Flow
[4]: https://openid.net/specs/openid-connect-core-1_0.html#Client... 9. Client Authentication
[5]: https://openid.net/specs/openid-connect-core-1_0.html#Refres... 12. Using Refresh Tokens
[6]: https://openid.net/specs/openid-connect-discovery-1_0.html OpenID Connect Discovery 1.0 incorporating errata set 1
[7]: https://www.rfc-editor.org/rfc/rfc7523.html JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants
[8]: https://www.rfc-editor.org/rfc/rfc7636.html Proof Key for Code Exchange by OAuth Public Clients
[9]: https://www.rfc-editor.org/rfc/rfc8693.html OAuth 2.0 Token Exchange
[10]: https://www.rfc-editor.org/rfc/rfc8628.html OAuth 2.0 Device Authorization Grant
[11]: https://www.rfc-editor.org/rfc/rfc8705.html OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens"
If you dont have patience for RFCs and specs, just go play with open sources IDP or better start from OpenID Connect client and server library, and try to integrate it into your "hellohell" app ;)
Very soon you will find out why developers keep building their own IDPs and how simple OpenID Connect can become a full time business
Go back here and here http://cryto.net/~joepie91/blog/2016/06/19/stop-using-jwt-fo...
And reflect yourself
Trust nobody
In terms of JWT vs other ways of doing this, is there any evidence that JWTs are more vulnerable that other approaches? Clearly there are vulnerabilities is other approaches as well.
I buy the statement that bearer authentication JWTs are much worse than proof of possession JWTs, but are bearer authentication JWTs worse than other bearer authentication approaches? What data would you need to argue that position
See, for example:
Decoding JWTs of web apps can give you a lot of suprises like the account email address or even password being stored inside them. For devs that don't know the difference between signing and encrypting JWTs are a footgun and even for those that do it can be confusing. The specifications are also a hard read. A handful of RFCs where you don't really know which one to lookup. JWT, JWK, JWE, JWA, WTF? It seems to do everything at once, signing and encryption, symmetric and asymmetric, and the format is always quite similar!
On the other side, if you are able to avoid the many pitfalls, JWTs can be very useful in the right place when used properly. You could probably write an equally or more secure JSON based token format from scratch without that much effort by just keeping it simple and restrictive/opinionated, though.
A similar question to the articles is whether you actually need Auth0 or not. You might but then you are offloading all the issues of JWT's to Auth0 so in theory, probably not in practice, you don't have to worry about those.
For many apps however. You probably don't really need Auth0.
And there's a good chance you really don't want to pay for Auth0. Their Enterprise tier becomes very expensive as your MAU starts to grow
This is how apps were doing it for literally decades, before JWT was invented. And most web frameworks will do all of this for you.
Yeah, exactly. I get to offload everything to Auth0 instead of maintaining a cryptographer on our payroll to reimplement a solved problem. Hand rolled OSS authz/n may work for individual projects, but there's no other reasonable solution in a modern enterprise environment with SSO.
> jwt as authentication tokens are constructed for Google/Facebook scale environments, and absolutely no one who is not Google/Facebook needs to put up with the ensuing tradeoffs.
The first sentence is factually incorrect. Google doesn't use JWT for most of its own authentication. Try analyzing their traffic - web and mobile apps - and you'll find that none of their 1st party applications / web use JWT. They do use JWT for OIDC, for third parties, which makes sense since with OIDC/JWT, third party sites can verify the token without talking to Google using a standard. This is largely similar for Facebook.
JWT RFC starts with:
> JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties.
Notice "between two parties". Of course, nothing stops from a single party to use JWT, but claiming JWT was invented for "Google/Facebook scale environments" implying they are using it for their 1st party authentication is just factually wrong. JWT was always meant to be an information transport between two separate parties, like OIDC.
"Ensuing tradeoffs" here is essentially about stateless authentication vs stateful authentication, and even there, this "absolutely no one" is simply incorrect. There are many scenarios where stateless authentication is sufficient, even for the authentication for a single party (let alone between two separate parties). Not all applications and use cases require the security properties of stateful authentication - a sufficiently short expiration would provide sufficient security in many cases, making the loss of any security worth it for the added benefit of simplicity and reliability with the stateless authentication.
Now with more nitpick:
The article claims, since "denylist" would require a database read, it's equivalent to stateful authentication. Theoretically that's true. In practice, denylist has some nice properties, that makes it worthwhile if server-side logout is the only missing feature you need from JWT. Denylist is often very small - if most users of your application or service do not explicitly log out, the number of denylist will remain very small. With the expiration, the size can not grow indefinitely either. Thus, the denylist you maintain can be much smaller than the record of all stateful authentication - which makes it cheaper/easier to replicate / cache across your systems than the full authentication records. So "denylist" is definitely a legitimate design option, with slightly different trade-offs than a full stateful authentication.
---
If I re-interpret the main claim of the article in the way I think would make sense, it would be: services should prefer stateful authentication, and the cost of stateful authentication is lower than people imagine it to be. That I can be behind 100%. As written, the article is at best some big exaggeration with incorrect details and hidden assumptions.
> The suggested pronunciation of JWT is the same as the English word "jot".
1 syllable instead of 5 FTW
> 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.
A JWT is like a keycard. If a hotel gives you a keycard to access your room, the card itself holds the credentials which give you access to the room. That's the core principle behind JWT.
If you want to ban someone due to malicious conduct, then you need some kind of ban mechanism but this is a concern of spam prevention and attack mitigation. You need such mechanism regardless of what session mechanism you use. With JWT, you may need to do 1 lookup to check the account object (you can keep an isBanned flag on the account itself), with regular session IDs, you need 2 lookups (1 for the account object and 1 for the session object). JWT still saves you 1 lookup... Also, if gives you a lot of flexibility. For example, you may be able to keep a 'blacklist' in-memory and separate for each worker (that may be appropriate for certain use cases); you're not necessarily forced to hit a datastore or database as claimed. Banning and spam prevention should be seen as a separate concern.
Also, at small scale, there are scenarios where you don't even need a blacklist. Maybe your app is being served on an internal private network or maybe it's public but it's not so critical that you can't wait for 10 minutes or 1 hour (or whatever) for the JWT to expire for the ban to take effect. I mean, with the physical hotel scenario as an example; your keycard remains active until it expires. There are many such scenarios in the software industry where a ban doesn't need to take effect immediately.
When you need instant blocking for DDoS protection (where immediacy of the ban/block really matters), you typically rely on IPs, not specific account or session IDs.
> 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.
As above.
> You need additional relationships between the user object and other objects in the database. You hit the database on each request.
The number of times you hit the database (and the reason for each lookup) matters and it's important not to mix up different concerns.
> Your service does anything at all with data in the database. You hit the database on each request.
This is just a variation which has the same flaws as previous arguments.
The author claims that Session IDs provide:
> Greatly reduced complexity. No need to manage a secure jwt signing/authentication key
You can provide the auth key as an environment variable when you launch the service. This is well supported in all environments at any scale including Docker, Kubernetes... anyway, 99% of services rely on various secrets like API keys so you need to pass secrets as env vars anyway, what's the issue with having just one more env var to hold the JWT signing auth key? There is no added complexity there as the mechanism for handling such secrets already exists in most applications. It's rare to see an application which doesn't have secret API keys... You need them for payments (e.g. Stripe API), for database API services (e.g. MongoDB cloud), Amazon AWS, etc...
On the other hand, managing sessions on the back end requires weird workarounds like setting and constantly refreshing expiries on the session keys or running complex cleanup cron jobs because you end up with complicated situations where your worker might crash and leave behind stale sessions. Session IDs are much more complicated.
Not to mention that session lookups becomes a single point of failure and can become a bottleneck for your application as you get more users. It's really difficult to scale well. You're going to run a Redis cluster just to manage sessions? Hello LATENCY!!! Welcome DDoS headache!!! Did anyone say backpressure issues??? How are you going to clean up stale sessions when a worker crashes? Good luck.
I hope you're billing for your development work by the hour, coz you're gonna need a lot of those! I hope your health insurer covers Panadol! Don't be surprised when GlaxoSmithKline opens up a new factory next to your office.
I think this is pretty far-fetched hyperbole. How do you think websites managed logging in before JWTs existed roughly a decade ago? They used databases and in memory caches, which work fine up to a surprisingly large number of users.