419 comments

[ 0.23 ms ] story [ 323 ms ] thread
Not to mention it's ridiculously easy to make JWT's insecure.
It's ridiculously easy to make any computer insecure.
Some computers make it harder than others.
Can you provide some examples here using modern JWT libraries? I'm not saying you are wrong or right, but this comment as it is written doesn't add much to the discussion.
Thanks for providing this, it is interesting vulnerability to read about.

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

> 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.

Contrast JWTs with PASETO implementations when you make that sort of analysis.

i.e., pick any that support v3/v4 and try to attack them the same way that JWT implementations have been vulnerable, or worse ways: https://paseto.io

Thanks for sharing this. I do a lot of work in this area and I had not come across PASETO before. It is an exciting project.

The nonce is especially nice because it makes the token high entropy enough that if only the signature leaks an attacker can't brute force the full token. This isn't always true in OIDC JWTs.

I have not seen an authorization server that makes it easy to configure no signing algorithm or even one that might be considered insecure. Most of the client authentication providers I have used (I.e frameworks) have also forced a secure algorithm, usually starting with rsa 256. So while technically you can use a no algorithm signer, I have never seen this happen.
So then why does Auth0 use JWTs for everything..?
Because their business is auth as a service and because their database is not your database and they don't want you hitting their database directly.

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.

As someone that is just attempting to get a better understanding of this aspect of a tech stack, when you say many apps don’t need Auth0 what is the actual alternative for the whole user authentication story?
Most web frameworks come with out of the box authentication plugins that will use your own database. It's plenty good enough and you still don't have to roll your own. Adding an external service adds complexity that you may not want to pay for later.
Submit a username and password via a form over https. Your backend hashes the password, and checks it against the stored (hashed) password in your database. If it matches, the user provided the correct password. Create a session token (random string is fine) and return it to the user via a cookie in the reply. Store the session token in your database, such that you can map it to the authenticated user. Then on each subsequent request, look up the session token and you have your logged in user.

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.

> the stored (hashed) password in your database

Hashed and salted

I hope

Technically yes, of course, but if you’re using an algorithm that requires you to manually worry about salting then you’re probably doing it wrong. Hence, “hashed” with an algorithm like bcrypt or scrypt is good enough.
There are some subtleties here that are important to know[0]. Which is why I generally advise people to use the framework provided code for this.

0: Such as use a secure hash like sha256 or blake2 instead of md5.

Yes, though I would recommend something that is resistant to timing attacks such as bcrypt. And this was why I mentioned frameworks in my last sentence. My intention wasn’t to give a comprehensive soup to nuts guide for what to do; instead i was giving a high level overview.
Auth0 provides a lot more goodies than this though. Password reset, organizations, multiple login flows, etc
Yes, and most authentication plugins for web frameworks provide the same things.
If you write a B2C app, there is a good chance that you might not need Auth0 and the functionality of the authentication, authorization, and account management tools in your framework suffice. If you plan on selling B2B you might need to support SAML and other enterprise federated login mechanisms. There, the scales tip in my book and I would go with Auth0. It’s expensive to support SAML in-house.
There are plenty of ready made SAML libraries out there that should work with whatever web framework you like to use.
There are plenty of libraries. But supporting a production SAML service provider takes a lot of work.

I've done this before. The first library I used had a horrible security issue that remained unfixed. We switched to one that seemed to be secure. Implementing SAML is non-trivial. Adding automated testing is also not something that required more senior people on our team. Getting engineers to understand how SAML work takes effort.

Also, about one out of five SAML IdP's are unconventional in some way. They are a royal pain to support.

The support burden of SAML is much higher than expected. Paying for Auth0 is cheaper than the engineering cost of supporting SAML, even with one of the existing libraries you refer to.

Need? Maybe not. But it's a time saver out of the gate.
> 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

>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.

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.

You don't need a cryptographer on payroll to install and configure an SSO library. Maybe your developers aren't skilled enough to do that, but it is not a challenge for a proper senior developer.
(comment deleted)
Well, I imagine, if we're taking the opinion of the article as fact, Auth0 must be able to scale to enterprise levels, i.e. have so many long-tail accounts that it approaches Facebook/Google scale.
JWTs are best practice for OAuth as it can transport claims. It’s up to your application if you continue to use it after the initial flow. You are fine to convert it, but most apps don’t as it’s easier.
JWT is a great way to authenticate the person who is trying to log into your app. Once authenticated, you are free to exchange the JWT token for a sessionid.
The whole point of the article was that JWT is unnecessary if you’re just using it to get a session token. Why not just cut out the middleman?
Jwts are fine, just use them properly.
Maybe address any of the issues raised in the post?
(comment deleted)
C/C++ is just fine, just use it properly.
Rolling your own crypto is fine, just do it properly.
You can implement a blocklist of all the revoked JWT and publish it to all servers. The list should be small, because only otherwise valid tokens need to be included. It becomes so much more complicated than a simple check-the-db setup though.

I don't think I would start with JWT if I did this again.

what would you do?
Sessions, like it's 2005 :P See above.
You're talking about tokens in general, not just JWT. The only alternative I know to tokens is to query the DB every time (or perhaps use a cache to make the lookup less often, but then you also have to find a way to invalidate the cache - back to square one?).
> You're talking about tokens in general, not just JWT. Yes, all stateless tokens. But I have never seen an in-house token system that was not using JWT's.

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.

Having to list the tokens to revoke is a problem in several ways. Obviously publishing the tokens themselves allows attackers to use them before relying parties get the revocation notice, so publish a token hash instead. But even publishing a token hash is annoying because you then have to record all the claims + token hashes issued, and that's a failure point. Instead you'd want to revoke _subjects_ (`sub`), but since tokens don't have to have those... (In enterprise systems though your tokens should name subjects.)
Using JWT from Keycloak just to obtain a session cookie. Is this a common pattern or a smell?
It's a good idea. The article conflates authentication with authorization. Your application can authorize many different ways, most do. You can use your session for authorization - your application can decide what a person can and can't do. But facts about the user's identity never change, like their uniquely generated ID in your database, and that's what gets stored in Keycloak's `sub` field, so it's fine to use that for trading a token for a session cookie. Their password does change but that's Keycloak's job, is to turn passwords into authentication tokens.

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.

The article isn't talking about authz at all.
(comment deleted)
Taking complete systems off the shelf and using them smells a lot like money. If/when your app requires enterprise integration, you'll be fanatically happy that you chose Keycloak over having to implement okta... then ldap... then.. Saml... then.. Kerberos... then ldap again with custom mappers..
Okay I'll bite...

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.

> so you can pass authentication ("claims", or whatever you want to call them) through your chain of microservice service-to-service calls.

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 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.

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.

Well a lot of value in application architectures like this is, I want to give something access to my Google Calendar forever, to schedule tasks and read stuff, expressly without user intervention. Most people want token exchange - that an all-powerful user token gets exchanged for a token with the privileges specific to the service that holds onto it. 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. And if I give a signing key to the service doing this deed for me, it's kind of redundant to an ordinary exchanged 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.

The first case sounds more like a case for OAuth which doesn't have to use JWTs or digital signatures.

> 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.

Which we have in modern computers and mobiles (the EU identity wallet concept is built around them).
I thought the EU wallet was using JWTs that attest to public key. You don't use them as bearer tokens, you use them as certificates and then do verifiable credential presentation via proof of possession and SD-JWTs.
Bearer tokens are vulnerable to man-in-the-middle impersonation.

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.

Man-in-the-middle impersonation is not the biggest threat because TLS 1.3 does a decent job of protecting the token in transit. The biggest issue is the endpoints:

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).

User X’s web browser calls Server A which makes a web service request to Server B that needs to authenticate that user X is making the call.

What types of tokens do you suggest in each case?

To pitch my own project, OpenPubkey[0], it is designed for exactly this use case. OpenPubkey let's you add a public key to an ID Token (JWT) without needing any change at the IDP.

1. Alice generates an ephemeral key pair (if she is using a browser she can generate the key pair as a "non-extractable key"[1]).

2. Alice gets ID Token issued by Google that commits to their public key,

3. Alice signs her API request data to Service A and sends her ID Token to Service A.

4. Service A checks the ID Token (JWT) is issued by Google and that the identity (alice@gmail.com) is authorized to make this API call, then it extracts Alice's public key from the ID Token and verifies the signature on the data in the API call. Then it passes the signed data to Service B.

5. Service B verifies everything again including that the data is validly signed by Alice. Service B could then write this data and its cryptographic prominence into the database.

Technically OpenPubkey uses a JWS, but it is a JWS composed of a JWT (ID Token) with additional signatures. OpenPubkey signed messages, like the ones passed via the API are also JWS.

I'm working on a system where each service in the path adds their signatures to the signed message so you can cryptographically enforce that messages must pass through particular services and then check that at during the database write or read. Using signature aggregation, you don't get a linear increase in verification cost as the number of signatures increase. It doesn't seem to add much overhead to service meshes since they are already standing up and tearing down mTLS tunnels.

The main question to me is how much autonomy do you want to give to your services. There are cases in which you want services to query each other without those services having to prove that the call originated from a specific authorized user.

[0]: https://github.com/openpubkey/openpubkey

[1]: https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey/e...

If someone nefarious steals your <secret something> then yes you have problems, this isn’t anything unique to JWTs
It makes a pretty big difference whether you have to send the secret down the wire to prove you possess it (e.g. a JWT, unlike say a private key).
The audience and scope claims exist to address that problem. Provided that RPs reject JWTs issued for other audiences than themselves there’s no security weakness here.

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.

It's also why we use DPoP for serious stuff.
I hadn’t heard of DPoP until this mention. Please tell us more. Google tells me it is Demonstrating Proof of Possession, but is it supported by any products?
DPoP described in RFC9449 - you can see from the RFC number it's quite new. I don't think there's wide support for it, but at least Okta supports it[1] and I think Auth0 are also working on adding DPoP.

Is it good? I'm not a fan. To use DPoP safely (without replay attacks), you need to add server-side nonces ("nonce") and client-generated nonces ("jti", great and definitely not confusing terminology there).

You need to make sure client-generated nonces are only used once, which requires setting up... wait for it... A database! And if you'll be using DPoP in a distributed manner, with access tokens then, well, a database shared across all services. And this is not an easy-to-scale read-oriented database like you'd have to use for stateful tokens. No, this is a database that requires an equal number of reads and writes (assuming you're not under a DDoS attack): for each DPoP validation, you'd need to read the nonce and then add it to the database. You'd also need to implement some sort of TTL mechanism to prevent the database from growing forever and implement strong rate limitation across all services to prevent very easy DDoS.

It seems like the main driving motivation behind DPoP is to mitigate the cost of refresh tokens being exfiltrated from public clients using XSS attacks, but I believe it is too cumbersome to be used securely as a general mechanism for safe token delegation that prevents "pass-the-token" attacks.

[1] https://developer.okta.com/docs/guides/dpop/nonoktaresources...

I agree that DPoP - especially the nonce - is quite complex, but I don't think it's as bad as you make out.

Proof tokens can only be used for a narrow window of time (seconds to minutes), so you just need a cache of recently seen token identifiers (jtis) to do replay detection. And proof tokens are bound to an endpoint with the htm and htu claims. They can't be used across services, so I don't see a need for that replay cache to be shared across all services.

The main issue for us was not the size of the cache, but distributing a guaranteed single-use cache (CP in CAP theorem) across multiple regions and handling traffic from all microservices that can read the token (we have hundreds and plan to support thousands, so I admit our case is quite extreme).

Please note that I am talking about using DPoP to verify _every_ request, not just a token refresh request (where OAuth 2.1 is setting DPoP as an alternative to issuing a new refresh token and revoking the old one). When using DPoP for every request, the amount of client-generated nonces ("jti"s) is quite high, since you need a new one for every request.

And yes, you can rely on "htu" to distinguish between services and have a separate nonce cache for every service, but this would require deploying and maintaining additional infrastructure for every service. Depending on your organization this may or may not be an issue, but this is a big issue for us.

What did we decide on instead? Request Signature and Mutual TLS binding (RFC 8705) where possible. Request Signatures without nonces do not work well for repeatable requests (like the Refresh Token Grant), but this is not our use case.

DPoP is an OAuth extension that defends against token replay by sender constraining tokens. It is a new-ish spec, but support is pretty widespread already. It's used in a lot of European banking that has pretty strict security requirements, and it's supported by some of the big cloud identity providers as well as the OAuth framework I work on, IdentityServer. We have sample code and docs etc on our blog: https://blog.duendesoftware.com/posts/20230504_dpop/
It's a new proposed standard. Where I work (in healthcare in Europe) we have it as a requirement for any new APIs we offer public access to. We have our own auth service, but looks like Okta already offers DPoP.
> The audience and scope claims exist to address that problem. Provided that RPs reject JWTs issued for other audiences than themselves there’s no security weakness here.

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.

Don’t see how those prevent tokens from being misused? They just prevent anyone from issuing tokens as you. Not by themselves, but if you implement your server correctly.
> Don’t see how those prevent tokens from being misused?

The purpose of a nonce is to explicitly prevent the token from being reused.

The purpose of the other claims is to prevent them from being accepted (and used) in calls to other services.

If you implement your server correctly, each instance of each service is a principal which goes through auth flows independently and uses its own tokens.

There is no token sharing.

> (or worse: using a shared-secret for signing tokens instead of asymmetric cryptography, ugh)

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

It limits your ability to compartmentalize your infrastructure, establish security perimeters, and provide defense-in-depth against vulnerabilities in your dependencies.
> If you don't have microservices then there's much less reason to use JWT's.

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.

(comment deleted)
Yeah I agree, but I think this post is for those cases where this design might be inappropriate, mainly monoliths with single dbs.

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

Side question, anyone knows where the phrase "used in anger" comes from? I know it means using something in production but where does it come from?

Is it about battlefield and such?

It’s military based isn’t it? First time you use a weapon “in anger” is to test its effectiveness in real world conflict
I've always heard it as a shorter alternative to "holy hell this tool is horrible but I'm unaware of an alternative, or cannot apply an alternative solution."
Are you sure? I have never interpreted it this way and it is the first way I hear this interpretation.

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)

My interpretation has always been the same as mceachen's, but other comments in this sub-thread have thought me I am wrong and your understanding is correct. Today I learned.

For what it's worth[0]:

> If you do something in anger, you do it in a real or important situation as it is intended to be done, rather than just learning or hearing about it

[0] https://dictionary.cambridge.org/dictionary/english/in-anger

No, it's a military term. Firing a cannon in anger for instance means firing at the enemy, rather than in training.
(comment deleted)
I don't think it necessarily means using it in production but rather using it on some non-trivial capacity that exposes you to it's various complexities and nuances such that you have more than just a surface level understanding. That probably coincides with using things in production a lot of the time, but that's not strictly necessary.
Yes it’s an old britishism - comes from “shots fired in anger” (as opposed to training)
Even with microservices, you still have the invalidation problem. I guess you could use non-Jwt for external auth and jwt between the services, but then you lose the benefit of standardization (and still don't get full zero-trust). Or you could standardize on jwt, but then, invalidation problem again.
It's pretty rare in practice to be able to make authz decisions solely based on the information in JWT claims. Space in HTTP headers is limited and any moderately complex system will have a separate authz concept anyways that can be used to check for token invalidation.
Exactly. Learned this the hard way. JWT is good for “this token is legit and has XYZ role or group”, and letting it go to the next layer. The next layer should do some addition checking that token has legit claims on modifying a resource or taking other actions, however that might be.
Depends on your business. Most b2b companies probably don’t need to care about invalidation, at least not in the startup phase. For B2C it’s going to be more important. But ask yourself “why do I need to pre-emptively invalidate tokens?”
Same applies to non-micro service architectures.
> doesn't seem to address microservice architectures at all

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.

> By just using a “normal” opaque session token and storing it in the database, the same way Google does with the refresh token, and dropping all jwt authentication token nonsense.

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

Aren’t JWT bearer tokens certificates already? Only the issuing server has the private keys, and the public keys are used to validate that server signed them?
This is the other way around. It allows the user (token holder) to sign messages "using" the ID token.

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.

Certificates use a signature to bind an identity to a public key.

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...

> OpenPubkey let's you turn JWTs bearer tokens into certificates.

This looks really awesome, thanks for sharing.

Browser sessions are not the only authentication scenario.

> 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.

I once operated a very low usage webservice that used JWT for auth. We got hit with a DDoS and it was trivial to mitigate by using AWS API gateway to drop HTTP requests that didn't contain a valid JWT for the IDPs we supported.

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.

This is a great point. 10 requests per second is likely to be sufficient scale that you are noticeable to people that might want to attack you. The ability to validate the key before doing anything with it could be a huge time (and resource) saver on AWS.
> The ability to validate the key before doing anything with it [..]

Q: What about the endpoint that's issuing the tokens?

In OpenID Connect the endpoint is issuing the tokens is run by Google, Microsoft or some other company that is too big to fail (or rather if it fails everything goes down).

If you are issuing the tokens yourself, you can build a simple horizontally scaling identity service that only does authentication and token issuance. With refresh tokens, if that service goes down it only prevents users not already signed in from signing in. Generally users stay signed into to webapps for weeks at a time, so you have massively reduced the impact: rather than 100% of your users not being able to do anything on your site, now 0.5% of users are impacted.

The notion that you have Google/Facebook scale problems at 10k requests per second (vs 10s of millions of requests per second) is a pretty funny claim in its own right.
A big problem not addressed when not using a signature based authorization scheme is that you need to hit your database for every access attempt. This makes you much more susceptible to ddos attacks.

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?

The client ID should be a primary key or some other indexed value, making those database hits fairly cheap. You'd also typically have some kind of rate limiting.
We use a rate-limiting rule in the existing firewall on the /auth endpoint. Our default is on five failed attempts in a five minute window gets you a one hour ban.
How do you fingerprint the requests? IP address?
The problem I had with IP address fingerprints is that we have a large number of customers all behind one hospital’s single IP address.
Two ways, default key is [IP address + User ID] we also have a fallback with a higher limit on [IP address] only when we expect lots of attempts from e.g. a VPN.
The issue from parent comment is that it needs to be on every access request, not every login request.
How do you think people make applications if every database call is suspectible to ddos?
1. Authenticate using signed JWTs at the edge via something like AWS API Gateway. 2. If the attacker is smart enough to use valid JWTs from IDPs. Find the JWTs that match the DDoS attack and ban those identities at the edge. This rate limits the attacker to how quickly they can generate new accounts on say gmail or azure. 3. If the attacker is can generate new accounts fast enough, add a bloom filter to the edge of accounts you have seen before the attack started.

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.

The article talks about when and how requests hit the database in both schemes, extensively.
While I could agree with the sentiment "consider using opaque auth tokens instead of JWTs in most use cases" I can't get behind the opinion-presented-as-factual-statement tone of the article. There are valid use cases to offload authentication to the client rather than verifying an opaque token with every request. One advantage is that community support for JWTs is large, but home-grown, ad-hoc opaque token solutions not so much. Another is that any claim at all can be stored in the JWT: IP address, user name, opaque token, whatever makes your app secure. While these same claims could be stored in a database, now you have extra overhead and maintenance dealing with them.

I would like to see an open-source project from this author that uses his proposed solution.

IMHO the stateful opaque token approach is simple enough that it can (and often does) get baked into whatever language/framework you’re using to write your app. In addition, the very nature of session tokens is such that the logic for what the token actually means/represents lives in your app, on the server.

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.

[1] https://github.com/heartcombo/devise

What? Session tokens have been around for like 30 years now, and every web framework worth using has a rock-solid implementation; you haven't had to do it yourself since Full House was putting out new episodes.
Binding a session to client details like IP addresses and user agents requires enough fiddling that it's no longer the no brainer to use session tokens over JWTs that the article makes out, is my point.
By "binding a session to client details like IP addresses and user agents" do you mean restricting the session so that only the original IP/UA can make use of the session token?

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.

> JWTs provide zero advantage

The article presents opaque session tokens - a key to a database table entry that is looked up with every request - as so clearly superior to JWTs as not to be a choice really, because session tokens are simpler and easier to use.

> 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.

We agree.

My contention is that after the specific use cases are accommodated, opaque session tokens are not the clear win over JWTs on the grounds of simplicity that the article presents it as.

When using a library that does all of the heavy conceptual lifting and conforms to standards, the level of effort is really 6 of one, half dozen of the other.

If I'm understanding correctly, your claim is: when a requirement exists for a niche "session lockdown" feature (likely required by less than a few percent of all sites/apps in existence), implementing the feature requires server side code. Therefore, the article is wrong about server side sessions being the clear winner.

I find this argument thoroughly unconvincing.

Furthermore, since JWTs are handled client side, and we agree that custom code is needed on the server for this niche feature, it's less likely that a single auth library would support both the basic authentication features and the server-side verification needed, meaning more "fiddling" is required with the JWT solution vs. something like Devise, which is almost entirely server side and can do most of the heavy lifting for you.

> If I'm understanding correctly [something else]

This is incorrect.

I would add two pros of jwts (I guess oauth 2 and oidc more specifically)

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.

3. Easy to implement. 4. Dont need to hit db. 5. Can store some information with the claim which is very convenient
How do you revoke tokens if a one gets leaked without hitting a db? How long are your users vulnerable to attack?
Use low TTL and put rate limits and other mechanisms in place?
I think you're right, but it seems like you get into a tricky territory that'll never be great (as everything with security has compromised). Too long is an issue for attacks, but convenient for users. Too short and you have to do an initial re-auth over and over again, partially defeating the benefits.

Even if the TTL is short, there are plenty of ways to compromise a token and use it immediately in an automated system.

If you're using JWTs, I'd lean shorter TTLs and embrace this as a potential concern. Not sure what the best re-auth frequency is though. I'd be really interested to see other's thoughts on that.

But the token is used over SSL and the only way to get it afaik is to hijack the client device or somehow hijack the server. The first scenario is pretty rare and the second is pretty easy to avoid. I don’t think that’s really an edge case that’s concerning for 99% of applications.
> the only way to get it afaik is to hijack the client device or somehow hijack the server.

Yet we have millions of passwords in dumps across the internet. Maybe hijacking the client or server is more common than thought?

I think you're conflating a few things.

Passwords being leaked is due to noobs and idiots in charge of systems, and probably not using an actual auth provider.

It really isn't that hard.

1. Send secure information over SSL. If some noob-tier dev decides that's too advanced for them, congrats I guess for being stupid.

2. Store passwords with hashing and salting. If you use an auth provider, like auth0 or firebase even, they will do this for you because they're not noobs. A noob-tier dev who stores plaintext passwords in a database with insecure connection and postgres:postgres is again stupid af.

Most of those leaked passwords are because of this. And don't think some Fortune 500 is not stupid. They outsource their development to Accenture or Detoilette, or any of the other outsourcers where they pay some "Software Architect" $8/hr to secure banking information (while charging the client $150/hr). I'm not throwing shade, but these companies are cheap af and throw bodies at the problem instead of experience and brains. I have direct experience with this, so I know how bad it is.

Don't confuse people being stupid (very common, 90% of any population) with a failure in a technology that others who don't understand it claim makes it inferior or something.

The internet and all the technology you use today is glued together by the <10% of people who actually understand computers, networking, and problem solving.

Just because a noob-dev uses session tokens doesn't mean it will be any secure if they fail those 2 points I mentioned. Those are so deadass simple to mitigate, anyone who purposefully skips on them should be named and shamed, and receive a 10 year cooldown from being a software developer.

Yes, this is the trade off. If you are working in an industry where you need to be highly sensitive for data access even for short periods of times then oauth/oidc/jwts are probably not for you. If you really need an emergency escape hatch you can always rotate your singing keys and jwks and invalidate all of your tokens and force everyone to sign back in.
Good point.

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?

(comment deleted)
> If a short session time isnt good enough, you can use a simple key store to check for revoked tokens.

It's not bad solution per se, but it does negate JWT's main value proposition, which is to not need such a store.

Another way to put it: "Do you want a (functional) logout button?"
That's not another way to put it. You can have a logout button which makes the client forget the jwt token.
But if the jwt was leaked before the client forgot it, the jwt itself is still valid and can continued to be used by an attacker.
I wonder if an extension to the concept of jwt that extends the cryptographics chain down into some hardware component such as a TPM or secure enclave is the right answer. Basically the payload of the token could contain a pubkey for checking a signature on the request payload. The logout button would then have two local effects on the client side: delete the token and tell the hardware component to forget the private key.
Well, hence (functional). Making the client forget a token sounds trivial. But there's a long tail of clients out there, and many cases where things might get more complicated.

Maybe I'm damaged from working in a regulated industry, but a (possibly malicious) client who went through the logout process and could prove someone else reused the token after logout might have a case. Or another of a myriad of unknown possibilities.

It's all very unnecessary. There's a reason we all used to invalidate trust server side. It's just so much easier to reason about.

Why would you need to revoke on logout? Forgetting seems to be enough in all cases except maybe SSO revocation because in all other cases you can (and indeed often must) trust the client to protect the credential.
Because logging out is also supposed to invalidate the token so it can't be reused by anyone who may have stolen it.

This thread is really making me despair. If you don't see a problem with JWTs, you aren't experienced enough to use JWTs.

I think you need to take this a step further and really define your threat model instead of being despaired :)

If an attacker is able to steal a victim's cookie database, their system (or at the very least, their browser) is already deeply compromised. It is very likely that an attacker with such capabilities could prevent your website from ever sending the logout request (install a browser extension which blocks it, inject into the render process to silently drop it, modify the cached JavaScript on disk to inject code into the site, etc.). The logout functionality only works insofar as you trust the client, and in any circumstances where the client's cookies could be stolen you really can't trust the client. So logout revocation is not really a meaningful security boundary.

How about the scenario of a stolen device that's logged into the service. The victim logs in on another computer to try and reset their password and lock the thief out of the compromised account.

This can't be done without revocation.

This is a different feature than a simple logout button though (something along the lines of "sign me out everywhere"), and many (smaller) websites don't actually support this. As far as I can tell, the very website we're on doesn't support it :) The parallel use case is SSO invalidation where you may need to immediately revoke access to a service after an employee is terminated.

You would need revocation in this case. Implementing this is easier when you already have database backed tokens, but unless you intend to support these features JWT is a totally reasonable engineering decision.

I feel ya.

You have to store invalidated tokens anywhere they might pass through a service, which means you have to persist them for as long you can predict that there expiry will last. Simply putting them in a memory database isn't 100% if that db gets flushed, and then you might start storing them in a disk database, which at that point, you might as well have just read the db in the first place using cookie auth.

In microservices, you generally have to put an invalidated JWT cache between every service, or compromised JWT's are just floating around your intranet.

I've worked at a plethora of places who have JWT's who have no invalidation strategy what so ever, the majority of developers think that when you log out and the user has forgotten the JWT then you are all good......

You can do what Google and everyone else does, which is store the revoked tokens. At scale this is easy to do efficiently and rarely requires a network request since the number of revoked unexpired tokens is small.
How does infrequentcy of revoked tokens reduce requests? Dont you have to check every token to see if its revoked?

Or Do all the server instances store a copy of all revoked tokens in memory/local db?

All the servers can store a copy or a bloom filter because the number of revoked tokens is small and doesn't change often
Store reset_time per user. Use a message queue (or postgres notify) to push changes to this value to your apps. Check the user's token was created after the reset_time when validating it.

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.

And hope your service hasn't been restarted so it doesn't lose the in-memory revocation list?
Just populate the cache when you need it? You will need a database round trip for the first request per user per application restart, if they haven't reset since. I assumed this was obvious.
Oh, I didn't realize that the design also has a database of revocations. In that case, you can just query that directly :P
You'll want to store your user credentials that they traded for a JWT somewhere. The point of using JWTs is that most of your requests don't have to hit this database.
You can use a separate DB that acts more like a cache for revocations- usually something where you can set a time to live on the row equal to the duration of the token itself.

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.

If I have to run a separate DB to check for revocations, why not skip JWTs and just use that separate DB for auth directly.
Not an issue for most cases but a cache of revoked tokens is going to be much smaller than a db of all users tokens.
The advantage of redis or similar kv DBs / caches comes in being lighter and faster than a full second database, mostly.

The secondary advantage is you don't need to deal with cookie storage, sticky sessions or anything else along those lines.

If you're manually hand crafting a server, go for it. If you're treating them like cattle not pets, going stateless with a bearer token tends to be easier.

How do you find out a single token got leaked within the token lifetime? If it's that easy to detect a leaked token, why aren't you stopping the leak in the first place?
Then you can hit the db.

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.

OAuth2 has no dependency on JWT, and _most_ authentication cases don't need OIDC, OAuth2 is enough.
I'd add that it's easier to abstract your software service from authentication. It's allowed me to write bridge signing from AzureAD, Okta etc, to a supported application deployed to differing environments on client hardware and readily integrating to different SSO systems.
JWT is not a protocol but kind of a message format from my perspective.

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

Oh man, he goes straight to stateful services as an alternative to JWTs. What an absolute nightmare, if JWTs are too hard stateful services are certainly more difficult.
Stateful services meaning the default way every web framework has structured a web application since 2005?
Longer, if we allow “web application” to mean “anything with a login, on the web”. All those popular forums, likely also stuff like yahoo mail, even gaming services (yes, browser-based game matchmaking services existed in the 90s, Microsoft ran one, among others) probably just because anything else would have been needlessly complicated and expensive.
Amazing how using a session ID stored in a cookie was entirely possible in 2005, but is somehow out of our reach with today's hardware.
A simple session cookie does not protect against CSRF. In 2005, session IDs were generated with low quality RNGs and too few bits making them easy to guess. OWASP happened for a reason.
It’s not hard to stand up, managing state across sessions and versions of your app is hard. For example a site I use frequently the Morgan Stanley portal is stateful and you can only be logged in from a single device/tab at once.

Most websites don’t need it, and it makes things harder to manage when rolling out new versions of your services. Life got significantly easier once I moved away from stateful services.

Yeah, and life was tough back then. My services have gotten significantly easier to manage since moving away from stateful services.

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.

(comment deleted)
You should not use JWT if you have a single application in your organization. However, whenever you have multiple applications, you need some form of central authentication / authorization service. Otherwise, you would have to maintain auth databases in each application, each application will need to be logged-in separately, you won't be able to implement a simple "suspend a user's accounts after X unsuccessful auth attempt", you won't have a central auth log.
External services use jwts pretty often, so if you have to handle jwts anyway, using jwts means that there's only one primitive, set of libraries, and concepts for your devs to know.

"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.

The problem isn't the mechanisms inside of JWT (though they are gross and worth avoiding on their own), it's the systems-level tradeoffs you have to make to use them idiomatically, particularly around refresh tokens and revocation.

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.

refreshing the refresh token is always a database hit, and in the case of using a third party like Auth0 or FusionAuth, you can invalidate refresh tokens at the individual and at the user level. Saying "the refresh token is the real token" as the article does is pretty misleading; the refresh token always goes to the same system that would accept the username/password, but the jwt itself gets carted around to other systems. So again, in the auth0/fusionauth case, the refresh token is sent to auth0/fusionauth, not your app, so even without any particular knowledge of what's going on, the application developer is forced to utilize them in different ways. There's a big assumption in this article that you're talking about a monolithic system where logins are processed by the same application that handles all requests. Even if you do prefer to structure your application as a monolith and avoid microservices, once you delegate auth to a third party or a system separate from your app, the bearer token versus refresh token thing starts to matter a lot.

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.

This still doesn't engage with the point. "Refresh tokens" are not a natural feature of every session scheme. They're required by stateless JWTs because JWTs are motivated by migrating authN into its own independently-scaled microservice, and because online revocation is difficult in stateless schemes. If you just use your framework session system, you don't ever think about refresh tokens. That's the point the article is making. It's not that JWT makes refresh harder, it's that it makes it a thing at all.
right so then the argument is less about "should you use JWTs" and more about "should you use stateless session tokens".

> JWTs are motivated by migrating authN into its own independently-scaled microservice

that's definitely one use-case, although I don't actually think having auth in a separate microservice under your own custody is the dominant use-case. The auth being in an entirely separate database means PII is in an entirely separate database, which can be a useful access control mechanism, or in the case of using a third-party auth service, means that there is no PII in databases under your custody at all. It makes the situation of "developers can access all data created by the software that they work on" really easy to implement while also maintaining "developers do not have access to all of the PII" as an access barrier. I think "I just use Auth0/FusionAuth and don't think about it" is actually the dominant use-case, and every third party offering that kind of developer experience utilizes stateless session tokens to make that happen.

> If you just use your framework session system, you don't ever think about refresh tokens.

right, but now the problem of keeping PII data access rules separate from your application domain data is an additional thing you have to engineer and think about securing, so I think the article is underestimating some of the negatives of that tradeoff; I've never seen a framework with a built in session system that did a good job of keeping access to the PII separate from the application data.

You wrote a comment upthread that misapprehended the post you were critiquing. I was motivated to offer some corrections. I'm less interested in the philosophical argument you now propose, except to say that PII segregation has only very rarely been the reason I've seen people adopt stateless tokens. It is still easier to segregate data using stateful token schemes than with JWT.

But I don't want to pretend we're still having the same conversation that started up thread. I assume you take my point, that if you think "I already have to do JWT so I don't save anything by not using them everywhere" rebuts the post, you've misread it a bit.

> if you think "I already have to do JWT so I don't save anything by not using them everywhere" rebuts the post

I have never believed that and I don't think my comment ever suggested I did, I mentioned "you might have jwt-handling tooling already" as one concern among many, not an entire argument. It really seems like you've honed in on a single point and, as you would say, misapprehended my comment. That one argument is not reason alone to use JWT and I really don't think my original comment ever implied I thought it was the entirety of the topic.

My impression is that the article author is following the good old "everything lives in my Rails monolith" philosophy from 10 years ago where login and authn is just another library including db migrations that you slap onto your monolith to get user management set up in 15 minutes.
in all fairness that's what 95% of web application projects really should be.
70% of the web is Wordpress, so yeah.
95% of the non-wordpress part of the web.

We’re rebuilding our microservice hell as a monolith anyway.

Having authn stateless whether with JWTs or not is a bad idea. By extension refresh tokens are a bad idea. Doesn't mean JWTs are a bad idea, if used as a general auth token they are fine. Implementing revocation of JWTs also isn't very hard but you need somewhere to store the revocation state.
Sorry, none of this is responsive to what I just wrote. I had a particular complaint about the critique I responded to, that's all.
I’m not sure what you are trying to say here? That dealing with refresh token expiry revocation for a single system (and two sessions) is better than dealing with expiry and revocation for 3?

I see some strong arguments for standardizing on a single one.

Some people made the distinction here, jwt on the front end vs the micro services across the network.

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.

The article misses the point of JWT: it's dead simple to implement.

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.

> And if he got his JWT stolen before logout, bah anyway any token could have been stolen that way, whatever the tech.

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.

If the user can somehow get their token stolen from the browser, they can also get their username/password stolen from the browser.
Plus, a compromised browser could just block the logout request. It's a nonsense and indefensible threat model. If you need to worry about compromised browsers, you need a mitigation which doesn't rely on the browser or that'd be easily compromised by someone who could compromise the browser.

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.

What if you want to implement logging out all user sessions?

You'd have to store something somewhere, so you end up losing the benefits of stateless authentication.

That's a fancy feature. The article is all about sticking to what is simple and without fancy features
No, it's a necessary feature if your credentials get stolen, which wouldn't surprise me considering how many people just cram JWTs into local storage, which is not the same as a secure cookie and has weaker security. Either you're setting the expiry time real low (which still won't stop someone who has four minutes 59 seconds left to wreck your stuff, because computers are fast), or you're maintaining a blacklist, which means, congratulations, you've just reinvented overly-complicated session tokens.
You could probably rotate the secret which would invalidate all existing sessions.
That would log out all users. That'd be pretty annoying.
Everyone’s talking about how you MUST hit the database for revocations / invalidations, and how it may defeat the purpose.

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.

(comment deleted)
So, basically a database where you store a replica in memory on every edge node.
Not really. A pub/sub bus cluster (or PaaS) is pretty different from a database.
I’m not talking about the pub sub cluster.

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.

Again not really. If the pub/sub broker is persistent, you don't have to persist the revocation list on edge nodes. And just pointing out that it's a db in some loose sense of the word doesn't help with the actual challenge of organizing the flow of data reliably in a federated system (i.e. one that can't share a single database).
For maximum scalability you'd want a bloom filter at each service for testing the token, and some central revocation lists where you go test the token that fail this.

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.

A former student of mine (Vera Yaseneva) redesigned our old auth architecture using jwts and I’m pretty happy with how it turned out. Maybe it is overkill for our simple autograder server, but it was fun getting it to work and I’m sure it is more secure than the old architecture which had many many flaws… it was a maintenance nightmare for years. After the redesign it has been a breeze. Here is the project https://github.com/quickfeed/quickfeed

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…

I'm thankful every day I don't have to work on a team where JWTs are a valid solution, and nobody suggests them regardless. What a nightmare.
It's the wrong question.

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 ;)

The beauty of JWTs is that, if you have to ask "do I need JWTs?", you probably don't need JWTs.
I’d like to hear a reply from someone at Supabase