Hacker News new | past | comments | ask | show | jobs | submit login
Why to not use JWT (2021) (apibakery.com)
157 points by thunderbong on Sept 29, 2022 | hide | past | favorite | 246 comments



> An old joke is that there are only two hard things in computer science: cache validation, and naming things.

Way to ruin the joke! The joke is that that are only two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

See, now it's a joke.


No - there are three hard things in computer science: cache invalidation, naming thi, concurrencyngs, and off-by-one errors.


Two threads walk into a bar. The bartender says, "I don't race want any like conditions time last!"


I really like this version of the joke, but it's impossible to tell during regular face-to-face conversation


You can pronounce it with the cardinals

1st Cache invalidation

3rd Concurrency

2nd Naming things

4th Off-by-one errors


That could work but it would be easy to get the timing wrong.

Also, nit: those are ordinals (since they specify the order). Cardinals would be one, two, three, four.


Naming is hard ;) But thanks for the nitpick


Cache invalidation, concurrency (say slowly), naming things, off-by-one errors, and concurrency!


and exactly once delivery, and exactly once delivery.


I would tell you a UDP joke but you probably won't get it.


I heard a good source routing joke but I don't know who to tell it to next


I could tell you a mathematical joke but that would be going off on a tangent.


I'm sorry. I missed that. Can you please tell me again?


If you add anything else, you ruin the "off-by-one" part.


Then there are three hard things


it was in the original joke.


I wouldn't say he ruined the joke. The original saying quite obviously only covered the first two issues. The off-by-one error was only added later. Though it definitely did enhance the joke factor.

https://www.karlton.org/2017/12/naming-things-hard/


But without the line about off-by-one errors it isn't a joke, just a straight forward statement.


The joke is that these two things seem trivial (perhaps even outside the scope of the real “problem solving” parts of the job) and computing is full of apparently much trickier problems. But in practice everyone repeatedly runs into trouble with the same couple basic issues.

It’s a joke because it’s not literally true – the other problems in computing can be incredibly hard, sometimes taking years of work to figure out – but like any good joke it does also have a kernel of truth and hopefully makes the listener do some reflection.

Adding “off by one errors” is a riff which only makes sense once people are already familiar with the original joke. It can be funny but also kind of misses/obscures the original point.


The best take on that joke I heard was like so. "Computer science has two problems: that we only have one joke, and that it's not all that funny".


I told my 12-year-old this joke last week, and she laughed out loud.

Then again, she had just (again) made an off-by-one error in her Python code, which she had variables named "stuff" and "things."


And scope creep!


My go to version is: there are only two hard things in computer science: cache invalidation, naming things, and cache invalidation.


As someone who's a little behind the times here, can someone explain the distinction between a session ID and a token? I'm referring to this passage:

> Sessions were built on top of cookies. The server kept context about the user's "session" [...] and indexed it with a random session ID. The session ID was sent as a cookie and for each subsequent request, server looked up the session state.

> Then came mobile apps and single-page JavaScript apps, and plain cookies and sessions started being inadequate. Developers started using tokens, where a token was an unguessable random string that behaved like an identity card. Possession of it proved your (client's) identity (bearer token).

Both a session ID and a token are random strings clients send with requests to indicate who they are. Why were sessions inadequate, and how do tokens solve this problem?


If you have a "true microservices architecture" as the article comments, you have a time budget for every request from the front end, through the graph of calls on the back-end.

Let's imagine a call-graph budget of 300ms. Every hop after the request lands on the back end takes a bite of that. Let's imagine a straight stack of calls three deep with no branching. Front-end makes a RESTful API call to Service A. Service A calls B, B calls C, and C queries a database.

Diving down to the bottom, the database call + networking will take 50ms (it's a big query), service C spends 5ms filtering, and another 10ms reorganizing the data into its response format and returns. 65ms eaten.

Service B deserializes (3ms) and acts on the data (10ms), then serializes the response (2ms). 80ms eaten.

Service A gets this back and chews on it for a bit to put it into shape for the front end. There's also a layer of proxies and caches the request and response goes through, so another 20 ms here. 100ms! Great! We're under budget (because we're not counting network latency back to the UI)!

Now imagine, that for every one of these hops, we add a 10ms call to a session service, and processing time (hopefully tiny) to review, and error handling. We've also only covered happy path, we've not considered call retries, branching calls to resolve additional data, each with a +10ms check for that session.

Now that time budget is starting to chafe. Validation of signed tokens with claims eliminates that 10ms tax on every call, and it is a big deal.

How do I know? I work on a system with hundreds of microservices. We use JWTs for back-end authentication. Thank goodness the article clears us to use JWTs. :)


Think you misread, they were asking about the difference between a session ID in a cookie, and a bearer token. Not a session ID and a JWT.

With a bearer token each microservice still needs to call out to an auth service to validate the token, right?


Funny thing about the terminology, JWT is just one kind of bearer token. The phrase bearer token in the first place simply means that if you have the token, assume you are who you say you are. (It's also shorthand for passing in the authorization HTTP request header with type "Bearer: ")

If you have an opaque token, then yes, you save nothing and still require a call to a central service.


If you share the hmac key or do private/public key signing you're able to distribute the public signing keys to your infra.

In doing so you just validate the token against the public key. You can then rotate these keys and have a list of them to validate against and age off keys which would be the last tokens expiration +1 day.


One bearer token can map to several session IDs.


Or one bearer token can have one session Id. Depends on how you implement.


There's nothing actually wrong with session ids in a cookie. It's that JWT was the new hotness and a buzzword. Now there's a lot of cargo culting around them.

JWTs are a signed claim that your service can trust, and use for authorization after just verifying the signature.

A session id, you need to take the id and look up your session in some mechanism on your backend.

JWTs are great for different services saying 'yes, this connection is actually this person, trust them.' Inter-server communication, oauth, etc, are good examples.

Session ids only work for the issuing service. Django sessions, php sessions, etc, are good examples.

One of the biggest problems with JWTs is that a lot of people are ONLY storing a user_id, or a session_id in them. On the server the decode the token and proceed with the standard session_id workflow, still hitting the db.

Invalidation is another thing. You still end up hitting the database, and if the JWT is only ever used by your own service, why bother?

Then there's people just shooting themselves in the foot and not actually signing them and just trusting them without any crypto signatures.


The "problem" is that JWTs are just a container format. There's nothing saying they even have to be signed, though they're designed to be more easily signed or encryptable. I've implemented the ridiculous SAML enveloped encryption/signature standard and canonicalization, and honestly, the simplicity of JWT while still providing for resilient security is a fine reason for its popularity when compared to some of the alternatives for federated id/authentication.

The right thing to do is use web standards (like OIDC) and libraries and existing tools (keycloak, ory suite, et al), same as with cookies. You can do the wrong things with both JWTs and cookies, and any other client-identifying technology.


>Then there's people just shooting themselves in the foot and not actually signing them and just trusting them without any crypto signatures.

You would be surprised, a very short time ago I saw a (pretty large/common) web FinTech service provider that included the private key to verify the signature encoded within the JWT. It was SHOUTING to be hacked. I don't understad how people can be so irresponsible.


You mean the public key? The private key is what's used to sign tokens, the public key is used to verify the signature and can be freely shared. Or maybe you mean they used a symmetric key and shared that?


Private key to SIGN the content.


what am I missing?

a JWT signature is the header and payload encrypted and attached. You would do that encryption via a private key, said public key would be used to verify it was encrypted by the private key.

What does "to SIGN" mean in this context?


I'm pretty sure he meant they were putting the private key in the jwt.

People do some dumb stuff. And the dunning kruger effect is all too real.


He said it was used to verify the token, which is only true if you use a symmetric key not a private key.


Right, from what they're saying, they're pulling the key out of the claims to verify the token.

No it doesn't make sense.


Because they got the code our of a reddit comment?


not sure it is new hotness anymore. Maybe in 2016, when it was still several years old and people still had tons of issues with it.


Session ID: Server has a list of valid sessions. Compares your session ID to its own list. Have session token? Access granted.

JWT: Server doesn't keep a list. Someone comes with a JWT, which is signed by the server. Server grants access purely on the fact that there is a signed token.

JWTs are useful if you don't want to sync the server-side list of valid session ids (many microservices for example).


That's not entirely true. Session cookie doesn't have to be opaque and can be signed and encrypted. That's how RoR been using it for long time. Realistically, you only need to make sure that an unexpired and verified session hasn't been revoked.

Opaque sessions are smaller, though.


It's a question of cookies vs HTTP headers. Cookies are hard to use with CORS (when using backend-as-a-service stuff like Firebase, in particular) and/or mobile apps.

There's really no difference between a random session id and a random token. But people typically think "session id == cookie" and "token == header".

More secure session cookies are also HTTP-only, which means JavaScript code can't access them. Which can be a security feature in a lot of cases, but makes SPAs harder.


I think they are trying to point out that a "session ID" is typically a key into session data stored on the server side, where the token is usually not just a key, but all the session data stored client side. With signing and other mitigations intended to keep that from being dangerous.

>Why were sessions inadequate

I suspect there are more reasons, but one is likely CORS and the tendency for the auth infra to be separate from the app infra.


note: most other answers compare sessions to JWTs, not the bearer token. i'm guessing you're asking about sessions vs. bearer tokens, not sessions vs. JWT.

----

i've been wondering the same thing. the only explanation i have are life times and explicitness. sessions are usually based on the browser session, i.e. cookies without an explicit expiration date are invalidated if the browser is closed. also, session cookies are always sent on any request (simplification) to the matching hosts. this means you have to re-login to get a fresh session every time you reopen the browser (unless you set an expiration date for the session cookie i guess).

i guess bearer tokens have a very long life time independent from the browser session and the user can invalidate them manually through some settings option. moreover, the token is added to the relevant backend calls manually (instead of automatically by the browser).

i'm not sure though how a bearer token would prevent CSRF. imo it's roughly the same as a session with a long expiration time.


> i'm not sure though how a bearer token would prevent CSRF. imo it's roughly the same as a session with a long expiration time.

You said it yourself. Bearer tokens have to be added manually, while cookies are automatic. So simply issuing a request to a backend will not include the bearer token, and you (as the attacker) have no way of actually gaining that token from your cross-site context.


as far as i understand, that's not necessarily the case; a malicious CSRF triggers a completely valid request by tricking the user to open an url while logged in. imo the principle works for both sessions and token based auth as long as the token based auth frontend application exposes an url that triggers the action.

ok, now that i think about it: providing an url that triggers the (malicious) action is a necessity for session based auth but only a possibility in token based auth.


XSRF is of the class of confused deputy problems; the site expects that the browser will only let the end-user that was authorized perform actions, but the reality is that the browser on its own has no concept of user choice or consent for web content. The hypermedia aspect means that content by third parties can by default initiate actions, which would override the first-party presentation by the site for user consent.

So XSRF protections make it so that third party content cannot successfully initiate those actions. Prime examples are:

- SameSite cookies, where the initial request from another domain will not have cookies presented.

- The local content adding session-related parameters unknown to third parties, such as an OAuth access token in a header or a session cookie repeated as a form parameter.

These protect against the third party being able to initiate actions and pick up any background implicit authorizations being provided by the browser, such as session cookies or HTTP Basic authorization headers.


bearer tokens are tokens passed via the http authorization header as a bearer scheme.

That's it. That's all they are. If they're not passed in that manner, they are not a bearer token.


I think the easiest explanation is this:

Session IDs are bound to cookies from a given origin. Thus, every request needs to include the original cookie, and have the same origin, to transport authentication data. But cookies cannot be accessed from JavaScript, only be sent implicitly if these things are true.

This made it difficult to work with separate hosts (www.my.app vs. api.my.app).

In contrast, Bearer tokens are explicit: JavaScript code can use and attach them to requests as appropriate, thus allowing for more control. The also are a little easier to work with (no reason to keep a cookie jar, stateless stuff works).

JWTs in turn aren't simply random strings anymore -- they contain some claims about the bearer, which are cryptographically signed, so whoever receives the token can be sure the claims haven't been tampered with. This allows for very powerful, interconnected services -- but most people use them to pass a session ID around, so... here we are :)


> This made it difficult to work with separate hosts (www.my.app vs. api.my.app).

This is still possible with session cookies if you use wildcard origin cookies i.e. *.my.app


I know, contrived example. Think of www.google.com and googleapis.com. Or CORS issues. There are legitimate problems with cross-origin requests.


Yeah, those are issues at google's scale. Almost no one who uses them is even at half of google scale.


The token refers to an API token or an OAuth token. These tokens are what backend systems require to allow a user to access data.

The problem is that these tokens should not be used by your JS code (they should never make it to the browser for security reasons). So how exactly does a user access their data if the front-end code running on their browser does not know the token? That's where sessions come in. The webapp hosting the front-end JS code (for example Node.js or Java-Spring) creates a session whenever a request is made. When the user logs in, their token (which the server retrieves though whatever authentication method is being used) is stored in the webapp as session data. That is to say, for each session, the webapp can store information about that session.... e.g. the token. The token is then used by the webapp to access the user's data via the API calls to the backend. The backend, will then use the token to verify the user has access to the requested data (or the requested resources).


I think it came from misunderstanding a lot of things by people making SPAs...

Session cookie is invisible to JavaScript, so handling unauthenticated state is a bit weird.

Bearer token is visible to JavaScript, so SPA can make some assumption about authentication status. However, it still must handle 401s because token could have been revoked or expired (JWT solves the expired problem). You also could encode some metadata like username and email and instead of having a single round-trip to fetch it once, now you will be sending kilobytes of cookies with every request.

CORS is a black magic to them. How to properly set cookie is also back magic - who would know that you can set cookie in way that would include subdomains, so it will be sent to www.acme.org AND to api.acme.org.

It was also weird to set a session cookie on outgoing requests when service a talks to service b.


Sessions are a unique identifier that index into some server-stored data, which requires the server to store that data. Tokens include the data itself (along with a signature that verifies it's actually from the server). For example, Discord bot tokens include the user ID of the bot they're associated with. So do user tokens.

With sessions, you have to keep track of every client that may be used to access an account. With tokens, those clients self-identify. You have to store less.

Some services unify multiple clients into one session but this is uncommon in my experience.


The way I see it is that session ids are handed out by the server as you login and revoked when you log out, so while it would be advisable for this session ids to be truly random there is no guarantee that this is the case. In truth I have seen sessions stored with an autoincrement id meaning that if you were to get a session id you could, in theory, just use the number before yours and highjack someone else's session.

A bearer token, in my experience, is a cryptographically generated random key (presumably unguessable) which the user can create, use and revoke at will.


if someone is dumb enough to use guessable session ids (instead of, e.g. uuids), then they'd be also dumb enough to use guessable bearer tokens.


To prevent guessing, the session ID must be cryptographically signed (and possibly encrypted) by the server, with the signature appended to it. Upon receiving it back from the client, the server needs to check it wasn't tampered with. This guarantees an attacker may not simply generate random session IDs until someone else's is found and their session is hijacked, because they wouldn't know how to fake the signature.


> Both a session ID and a token are random strings clients send with requests to indicate who they are. Why were sessions inadequate, and how do tokens solve this problem?

JWT tokens are not random strings. They contain claims about the bearer that are cryptographically signed to prevent the bearer from altering them.

https://jwt.io/introduction


yes, but they ask about the difference between session ids and bearer tokens (NOT jwt).


Session IDs were short, so it was easy to randomly guess them. You would get access to some random account.

Also randomly generating them would result in colisions if you had a lot of traffic.


If your session Ids were short enough to be guessed then you were doing it wrong. The simplest thing to do is just use a unique GUID as a session id but a sufficiently large random number would do just as well.


At first they were short, and you could get one to see the format and then just try to enumerate some random numbers around that value. Without rate limit for the requests, it worked.


I have happily used JWTs as signed objects that can be easily parsed and validated by any API consumer, in any language, using library code.

These objects can be transferred through untrusted channels, and retain their verifiability. That can be useful when moving data between organizations, or systems that do not talk directly (e.g. a mobile app that interacts with multiple unrelated backends).

RFC 7519 says:

  JSON Web Token (JWT) is a compact, URL-safe means of representing
  claims to be transferred between two parties.
Not all claims are authorization claims. Not all claims require ad hoc invalidation. Some claims can even be permanent!

Authors ad infinitum have pointed out the risks in using JWTs for authorization (linked web article says "authentication", but both could apply).

Those risks are:

  - Invalidation/expiration controls are limited
  - Receiver might not properly validate signature
  - Protocol dumbly allows "none" algorithm
Only the first is an operational concern, the others are just "bad code works badly" problems.

The moral of the story is: If your claims do not fit the timed-expiration model of JWTs (e.g. some authorization claims), then don't use JWTs!


> the others are just "bad code works badly" problems

Software should be hard to misuse. If you have a library intended for universal usage, and the library users have to remember to add some code every time they call it, then it's a bad library.

If you have an specification intended for universal usage, and the implementers have to remember to always do 3 operations together, than it's a bad specification.


Right, but rm -fr / is also easy to write, but is pretty bad code. JWTs and statelessness have their uses, building in refreshing is not that difficult (if it's not built in already), and using "none" well, just don't use that.


There are alternatives that won't accept "none" or broken encryption algorithms, and that won't allow you to access the token data without verifying its authenticity.

JWT is just a bad standard all around.


Many issues of JWT are actually issues on library for nodejs to work with JWTs and partially the language: trick server to use public key as secret to validate JWT, trick server to use none.

JWT itself is also poor by having things like "none" at all. Which means in every valid place to use JWT, you should really be using PASETO.


You're missing the core challenge of JWTs, which is revocation. If you just check the signature then users can't log out.


Revocation == invalidation.

User-initiated log out is easy, you just clear the token. Promiscuous token gathering is harder (but SSL..!) and makes the case for invalidation or short expiration periods. And forced logout is the legit operational problem case.

Again though. If JWT does not fit your authnz model, don't use it. JWT works well in many applications outside of authnz, and some inside it as well.


What I'm pissed about is that everyone hopped on the JWT train too fast a couple of years ago.

Now it's really hard to argue with architects / developers why cookie authentication / bearer token makes more sense than JWTs.


> Now it's really hard to argue with architects / developers why cookie authentication / bearer token makes more sense than JWTs.

Because that's a nonsensical argument? JWT is just a token + validation. Nothing more. You can use JWTs in cookie authentication, you can use them as bearer tokens. The only thing JWTs are doing is carrying a payload and signing it.

Now, if you want to talk about Oath2 or OIDC then maybe there's a different argument to be had.


> Now, if you want to talk about Oath2 or OIDC then maybe there's a different argument to be had.

I imagine that is the main argument. People use JWT because it's standardized on the authentication protocol... The same authentication protocols that are horrible in many more ways than simply using a bad token format.

Yet everybody jumped into them when Google commanded.


Why would anyone use a jwt in a cookie? A sensitive cookie should be encrypted. It does not require any signature.


I feel like this is mixing concepts.

JWTs aren't for transmitting sensitive data or encrypting things. JWTs are about having claims that can't be forged by the client.

So why would you put a JWT in a cookie? To give the end user a set of claims that they can't change, they can only read and give back to the server. Those claims can include things like user id, or session id, or whatever you might imagine.

Now, could you accomplish the same thing with an encrypted cookie? Absolutely. I'm not arguing about what you can or can't do with stuff. But rather again commenters saying "JWT is such and such and forces so and so". It's nothing but a signed set of claims.


JWTs can be encrypted and signed, or just signed (or signed alg: none).


Ah, didn't realize encryption had made it into the spec.


stop being snarky, there are RFC's describing JWS and JWE, people just use the term JWT generically and let the context dictate what they're actually referring to.


You are reading my comment uncharitably.

I did not know that encryption had made it in the JWT spec. My comment was supposed to convey surprise, not some sort of snark or sarcasm. I read the spec after the OP comment and learned something new today.


Because cookies can be set to HttpOnly, making them inaccessible to JavaScript. They're also automatically included in all requests, so if you want to download a file that requires authentication, you don't have to do some convoluted JavaScript trickery to accomplish it. It can just be a regular link.

I feel like the people who bash JWTs have never actually built a real-life application using them. Yes, there are footguns. But they are dead-simple to mitigate, and they are far from the only footguns in the world of web applications.

Yes, you can't fully "log out" without a centralized database. But who cares? A JWT in an HttpOnly and Secure (requires HTTPS) cookie is very well locked-down. I'm not worried about an attacker being able to retrieve it, because if they can then the client is pretty well owned at that point and the attacker can do whatever they want.


But why not use a session cookie also with HttpOnly?


I deal with applications that run multiple instances. To support that, it would require either an affinity cookie to "stick" the session to a single instance (which could go down at any time based on load) or a centralized session database. I don't like either of those options, so I use JWT cookies.


Yep. Not everybody runs at FAANG scale.

Have a B2B product where you'll have, maybe, someday, and I'm exaggerating here, X00,000 DAUs? Just set up cookie auth and track the sessions in Redis. Session revocation is super-super simple and you'll easily be able to handle any security vendor questionnaire asking how you lock out terminated accounts.


JWTs aren't even hard to argue against in terms of logic. What is hard to argue against are developers whose theology includes JWT.


I agree. Not enough critical thinking was happening when I saw devs start adopting JWT without clearly stating why, other than "current best practices is to use JWT... end of discussion".

My concerns with JWT from early on is that the data stored in them was potentially stale. Front-end developers would always request fresh data at each interaction. Second, the JWTs were so long. We had to keep passing these long JWTs around.... mainly for testing stuff out, we had long lived tokens, especially in dev, so I think we passed them around to replicate API calls. So you felt how long they were.... and in my head I kept thinking about all this useless data being passed around taking up CPU/network/memory resources. So I would just remove JWT and replace the tokens with UUIDs. Everyone was happy about it, but they were confused as to why they were needed in the first place. I would just respond with, well when you find out let me know and I can add them back.


How would you handle user authentication on a serverless platform? JWT are imo perfect for that since they are stateless.


How do you handle logouts?


Issue tokens that have a reasonably short TTL - say, half an hour - and let clients use their refresh token to obtain a new token after that. On refresh requests, ask the database whether the refresh token has been invalidated, if it is, return 403 (bonus points for checking the expiration date first and delete expired and invalidated tokens from the invalid-list).

This reduces the necessary database roundtrips, while still supporting a logout flow.


This also means an attacker can be running around with a compromised token for up to a half hour before they're stopped.


If that is unacceptable for the business case, it's probably clear JWTs for sessions are unfit for the particular task?


Is it acceptable for any business to allow accounts to be compromised for a half hour?


I'd actually argue that yes, that is acceptable for more businesses than you might think. We're talking about a session staying usable after logging out for 30 minutes in the worst case.

For this to be exploitable, you'll have to jump several other hoops, like accessing localStorage of another application, for example.


I'm not the person you replied to, but in most implementations I've seen - they don't. Front end or app is politely asked to delete the token to simulate the user logging out, but the token isn't revoked in any meaningful fashion.

No additional checks are performed on the back-end to verify whether the token has been revoked as that would reintroduce a round trip to the database you're trying to avoid in the first place.


Well, when dealing with OIDC / OAuth you can bind the tokens to the user session or trigger back channel logouts. But anyways its not really easy to tell an RP to stop using a token.


No one - OP included - is arguing that there are no good use cases at all. Just that most situations don't call for it, and you're better off with something less complex.


Hallelujah! I've been saying this for years and people looked at me like I was insane. JWTs are one of the biggest cult/hipster scams of the last few years.

GraphQL everywhere is another one (like JWTs, it has its uses but people went way overboard) and Tailwind is the next one.


Microservices and Serverless are another two.

IMO the only hyped up piece of tech that was actually good in the last decade is containers (but not the orchestration, just containers).


Serverless? Huh I can't get onboard with that one.


Serverless is great for scenarios where you don't want to have to worry about scaling.

AWS S3 was probably the first "serverless" technology to really take off. You don't have to worry about how many hosts you need to adequately handle sharing a file with millions of people. You don't need to start up multiple hosts in order to get backups in multiple data centers and then handle synchronization. Nope, just put a file there, and everything else is handled.

AWS Lambda and other FaaS providers made it so you don't have to configure scaling hosts to handle sudden large loads. It's also great for things that only need a couple minutes of CPU time per month, like say, periodically processing some data in batches. You don't have to deal with standing up a host that runs a cron job and then paying for CPU time you're not using.

Serverless databases mean you don't have to worry about patching the underlying operating system.

Of course, serverless has its limitations. FaaS is not suitable for long-running tasks, for example. It also tends to result in vendor lock-in.

EDIT: If you're going to fire back with "AlL tHoSe ThInGs ArE sTiLl RuNnInG oN sErVeRs", don't waste your time. It's a tired argument that misses the point. Yes, they run on servers, but you don't have to care about the underlying servers.


Infinitely scaling webservers is trivial. Serverless solves a problem that is already easy to solve.

Infinitely scaling database means inevitable tradeoffs. Serverless doesn't take those tradeoffs away.

It doesn't do anything to justify the hype.

You get complexity, worse DX, vendor lock-in and often higher price for the sake of not having to do security updates. This is the worst deal in the history of deals, maybe ever.


Your entire last sentence, except the higher price bit, is just completely utterly false on its face. Serverless is magically simple - and that is DX, magically simple and easy to reason about. Vendor lockin?? Wat? Again utterly wrong and false. The whole point is you are running your code, you define the function bodies, they just get deployed to different serverless hosting services and it is insanely simplifying. Switching from AWS to Netlify to Vercel is not harder than switching under any other approach.

About cost, even though that isn't "completely utterly false" it is misleading. If you are a startup and want to move as fast as possible without incurring tons of tech debt, and don't yet have millions of daily users, you should use serverless functions because the total cost will be relatively small (relative to the insanely high cost of good devs that is -- always optimize for DX and velocity until you actually have scale). If you ever get to the point where serverless is costing you real $$, then refactor then - you've incurred almost no tech debt by waiting and being very very fast prior to that may just be the reason you got to that scale at all.

Go ahead and build a serverless app utilizing a framework that is well aligned with that approach (may I recommend Remix?) and I bet you'll change your tune. My gut tells me you have no firsthand experience because your points are absolutely off base.


You're still not addressing my points.

What is the big problem that serverless solves?

What does it promise and what does it actually deliver?

Infinite scalability? It only solves the easiest part of it.

Cost reduction? As you seem to admit, generally this is only true at small scale.

Security updates? Fair enough, but this has never been the real problem. If you're at a point where security updates are that important you will unlikely leave them in the hands of a third party.

My bet is that what you're attributing to the benefits of serverless are not inherent to the serverless itself and are just benefits of infrastructure built around it.


Yeah, somehow "scale" + no server? ("serverless") combined catches VC fishes. I was exited about edge compute but it's been hijacked by these js engine hype that run function on serverless. It becomes indistinguishable between a concept of "compute close to user" and this serverless + js engine thingy.

I wouldn't be surprised if large portion of serverless market is javascript. And rust driven wasm. So monoculture right now.


I see a lot of the comments in this thread from people that apparently just do not get the problem with JWTs. I was using "presigned access/bearer tokens" way before JWT was a thing.

There's multiple safe and easy ways to do authentication/authorization without providing a signed green card to the client which you don't have the ability to cancel at any time. As long as you keep control in a database.


Kinda agree. Tailwind is fine, I think it's over features at this point, I love framework to have some sense of "done" or stable.

GraphQL is side-effect of spoiled-by-VCs frontend ecosystem. Frontend folks love it doesn't mean it's good for end-to-end system.

JWT, I don't have enough experience on it, so I've never been sold.


What's wrong with tailwind? It made css styling accesible and easy at least for me.


I agree, Tailwind seems to be out of scope here for comparison. One is about server architecture, the other one is about how to write CSS. Both have a completely different impact (JWT as a footgun vs. Tailwind improving/worsening developer comfort)


This is interesting because one thing people ask me when a system is using JWT is in case they reset their password, how can they invalidate all existing sessions? They basically can’t.

Using A cookie-based/token-based session strategy, you can do that quite easily.


JWT is a token, often stored as a cookie. I don't see how it's impossible to invalidate a JWT but easy to invalidate a cookie or token.


If you treat the JWT as a random token and check whether it is valid in your central database every time, then yes, it behave like a random token and you can invalidate it very easily (just remove it from the list of valid tokens). But then, why use a JWT instead of a random token?

If you treat like it is supposed to be treated and check the cryptographic signature instead of going to a central database, then you can't invalidate it (at least before it expires). You can ask the client to remove it, but what if they don't? (eg. they are an attacker)


JWT is a token that is hard to forge with a payload.

Meaning, if someone gives you a valid JWT that says "I'm user 123" you can trust that this is user 123.

That's all it is. When reading in intent on how it should be used or what you should do with it, that's something beyond what the thing actually is.

So any pattern you can imagine with a session ID, you could implement with a JWT. Just throw the session ID in as part of the payload. Just because you are using JWTs doesn't mean that you don't have to work with any sort of external resource while you are processing it.

What purpose does it serve then? You can still avoid lookups when a JWT makes a claim. You know the JWT is valid so you know that all claims made within it are true. If it claims "I'm user 123" then they are user 123. If it claims "My account number is 435" then that's the account number. You don't have to make a lookup to see those facts are true. If it says "My session is 433" then you can validate that session 433 is still active and act accordingly.

JWT isn't a panacea but it also isn't anything more than a bag of claims. It doesn't claim to be anything else either.


> If it claims "My account number is 435" then that's the account number.

What if the account number changed since the token was created?

This works only for immutable claims. If the truth behind those claims changes in the mean time, you have to be able to invalidate the token, or accept the fact that it serves stale information.


> If the truth behind those claims changes in the mean time, you have to be able to invalidate the token, or accept the fact that it serves stale information.

Isn't that obvious? I mean, if we were talking about sessions and you put in the session set information that can change outside the session, wouldn't the same problem exist?

I just don't see this as a fundamentally unique problem for JWTs.


> Just throw the session ID in as part of the payload

An authenticated session id is just a very very long session id.


    JWT is a token, often stored as a cookie.
This is both true and also misses the point. Session Cookies are ususally stateful. In other words, the server validates the session by querying a service or database. JWTs however are stateless. You don't query a service or database to validate the service. As a consequence if the JWT is not expired and is still using a valid key there is no out of the box way to invalidate the JWT. You can build custom invalidation measures on top of JWT but it's not a natural consequence of the design in the same way that a session cookie is.


If you are using a cookie and managing the session expiration on the server, you can kill it anytime.


Throw a session ID in the JWT and do the same thing.

JWT has a freeform payload that can literally be anything you want. Nothing about it prevents you from using a pattern like this.


If you do this you get 0 value from using a JWT over a regular session id. It's simpler and likely less work to just not use a JWT in that case.


The value add is you don't need a cryptographically secure session id generation mechanism when you work with JWTs.

A simple session ID can be (and often was) forged to grant access to things end users shouldn't have. Put the session id in the JWT and now an attacker can't easily change the session they are associated with.


generating a sufficiently random session id is not difficult. Using JWT doesn't substantially make this easier for you and operationally you are going to have to invest in significant key management infrastructure. If you don't need the stateless nature of JWT you should just not use them.


> generating a sufficiently random session id is not difficult

There are enough CVEs on this specific topic that I tend to disagree. I've been around on the internet long enough to witness a HUGE number of session hijacking attacks. [1]

> Using JWT doesn't substantially make this easier for you and operationally you are going to have to invest in significant key management infrastructure.

IDK about making it easier, but rather using JWTs for this makes it harder to get wrong.

> If you don't need the stateless nature of JWT you should just not use them.

Agreed. Use them when they make sense and not when they don't. I'm not arguing that JWTs are panacea. They are neither good nor bad, just a tool.

[1] https://owasp.org/www-community/attacks/Session_hijacking_at...


> I've been around on the internet long enough to witness a HUGE number of session hijacking attacks.

JWTs don't make it any harder to hijack sessions, in fact, they often make it easier.

Session sniffing and man in the middle attacks don't discriminate between JWT and cookies/session IDs. Nowadays they are very hard to achieve with the vast majority of the web operating over HTTPS with optional HSTS.

Cookies containing the session ID can be marked as HTTP only, unlike JWTs which are often stored in localStorage, which makes them vulnerable to extraction via XSS vulnerabilities.

> using JWTs for this makes it harder to get wrong

Any popular web framework these days should provide secure built-in functions to generate and validate cryptographically secure signed cookies containing the session ID.

JWT libraries have had critical vulnerabilities in the past, such as allowing usage of the badly designed "alg: none" feature of the JWT specification.

https://auth0.com/blog/critical-vulnerabilities-in-json-web-...


JWTs have been in the news a lot for trivial vulnerabilities and among security/cryptography professionals is widely regarded as a problematic standard. I would argue just as you seem to be that the surface area for ways to get them wrong is much worse than session tokens.


You do need a cryptographically secure RNG to work with JWTs though. There's no getting around the need for randomness.


You only need a cryptographically secure RNG to generate a key pair. Assuming asymetric keys. That private key can then be used to sign many many JWTS.

Whereas generating new session ids will always need fresh entropy.


That depends on the algorithm, some depend on randomness like IVs.


Maybe you are thinking of JWE?

If the signing of the JWT wasn’t deterministic, then it would be hard to verify.


Correct. Some auth systems use JWE. Some even use it interchangeably with just JWS.


You can track JWTs on the server, via a sessionId or whatever you wish, it just breaks the intended pattern. If you have to do a lookup on each request (necessary to invalidate the token imperatively) your JWT is no longer stateless which is a core tenet of the JWT approach.

It'd be like building a React app and calling getElementById(id) to update DOM values. You _can_ do it but...


> breaks the intended pattern.

Who's intended pattern? Where is this stated as being the "right" way to use JWTs?

I'm seeing a lot of claims about the intent behind JWTs but frankly I think it's because people are skipping over having a fundamental understanding about WHAT JWTs are and instead are cargo culting on what they believe they should be.


My perspective is that I had recently realized that I didn't have a great justification for having used JWTs in my last two projects (and worried I had been part of a cargo cult myself). Truly.

I can't see their value against a bearer token + session tracking on the server for most cases (e.g. it won't be a huge performance hit to do a lookup of some sort on each request).

The two apps I'm referring to have a few thousand users who only make occasional requests.

I think a lot of apps fall into this broad category and I don't see what extra value JWT is providing. Encoding user data is pretty convenient (though more opaque) but if you want to be able to ad-hoc invalidate them you need refresh tokens or a session list. Not only does that re-introduce needing to do a sort of lookup and server user tracking, the encoded data on the token is no longer a positive, since you bifurcated knowledge of the user (token + list), and all its data would be more discoverable by including it where you are now tracking sessions anyways.

Help me out if I'm missing something. My mind is open.


It’s pretty simple to check a JWT’s issued time again a timestamp representing the last time an account changed password / revoked all sessions.


If the password is changed then the encryption on the JWT is no longer valid though. So it is unusable at that point.


I believe GP is referring to user password not the encryption key. Rotating the encryption key would invalidate all previously minted JWTs. A user changing their password would not because it is not used to sign the JWT, therefore the old JWTs would still be valid until expiry after a user changes their password.


It's my understanding that user's password + a server secret = the encryption key. Changing either one of those will invalidate the token.


Having to look up the dynamic signing key reintroduces a trip to the database. At that point it's nothing more than a session ID with extra steps.


Why in the fresh hells would you store a users password in plain text, in order to decrypt that key?

Even if you didn't you still need to retrieve the user and password from some storage to validate the key, which invalidates the reason for JWTs in the first place, since you supposed to be able to validate them without access to an auth service/db


Plain text?? A user's password is hashed...You don't need a paint text password to compare text encrypted with it to other text encrypted in the same method. Either they match or they don't. The user's hashed password doesn't leave the server and plain text is never involved.


If you use the password's hash as a key, the plain text no longer matters, because the hash is now the thing that an attacker needs to forge a credential. So your database is effectively storing the real password, as if you had not used a hash.


I think I see what you're saying but how would that be useful still? Like is an attacker has a hash of the users password they still don't have the server secret.


The users password isnt involved with the encryption of JWTs


If the user's password isn't involved then what's the mechanism for invalidating existing tokens?


There is none.

And now you're starting to understand why JWT's aren't worthy of the hype. A truly stateless JWT implementation is insecure since you can't invalidate existing tokens.

The usual compromise is make JWTs have a short (<= 15 min) expiration and provide the client with a refresh token that doesn't expire, but is stored server-side. When the user logs out, the refresh token is invalidated server-side so a new JWT can't be issued. You're re-introducing state with this solution, but you're making it so you don't have to bang on a database with EVERY request, just one every ~15 min or whatever your expiration window is.


One could include the SID claim, with this the token at least could be tested against a users session on the IdP/OP. (This is being used in the ID_Token in OIDC)


There is none. Personally, I use it for authentication on a platform where the user shall be logged out after 30 min anyway.


What? Why whould the signaure be invalidated?


Not the cryptographic signature; they mean the authorization.

I.e. if the user's password has been compromised, then the attacker with the password could have started a new session using it. The question is, how do you (allow the user to) revoke the auth that was granted by the compromised password and invalidate that fraudulent session.


It seems like most of the commenters here are blindly choosing a side when the real answer is, it depends on your priorities. If you want statelessness and one fewer db calls at the expense of timely revocations, pick JWT’s. If you want instant revocation, then accept that you need to check a database every time and don’t use JWT’s. Both options are correct.

As an aside, if you do pick JWT’s, you could create a revocation queue, publish all revocations to that queue, and read that queue asynchronously in every server to keep a token blocklist. You lose statelessness but retain speed (no db call).


I think this article is shortsighted: what about not needing to store token at all? I think that is one of the biggest strength to it - you can issue short lived tokens in high volume without needing to store anything anywhere.


Not super-opinionated on this, but let me try to steelman the JWT case.

Yes, to perform session invalidation with JWT requires some amount of centralized state, so a perfectly-decentralized auth system is not actually possible.

However, using JWT means this centralized state is much less heavy than traditional sessions. Instead of saving ALL sessions and ALL session data on the server, JWT only requires saving a small fraction of session ids (those that have been recently invalidated) and NO session data. By using JWT, you have simplified your memory needs and reduced them by multiple orders of magnitude.


Not saying you should use JWT, but you can invalidate tokens by adding a timestamp to the payload. This way you could invalidate those issued before a certain time and logout users.


The overwhelming majority of JWTs have an expires field. That's not the issue.

The idea is that you have a disgruntled employee with a token that expires in 5-10 minutes. You don't have invalidation checks, so what damage can that employee do in 5-10 minutes? Keep in mind for many companies, exporting a client list is a big deal.

Why would you not have validation checks? The point of a JWT is they're stateless. When you get one, if you have the key, you can validate it without access to the auth service.

If you take your JWT, and then are using the claims to check the database, it is 100% functionally the same as a session token.

JWTs are not something you issue to use on your own service. It's for another service to verify the user on their service based on a factor coming from your service.(yes i'm aware there are other uses)

If the only thing in your payload is a session_id or a user_id. Stop using JWTs.


> what damage can that employee do in 5-10 minutes?

This is not an authentication issue (JWTs) this is a classic authorization issue (Permissions/Roles).

It's not the authentication layer's fault if you allow everyone root access.

JWTs are just fine. Bearer tokens are just fine. You can write shitty session code just as easily as shitty OAuth2 code.


To clarify, what I do for sso stuff, if I get the JWT, verify it, then generate a session_id to hand back to the frontend. I'm essentially using the JWT as a replacement for username/password when it comes from a sso provider. I can then do invalidation or deny login as normal.


You're absolutely right, but then we reach the, "why are you using JWTs?" part of things. if you're issuing them for your own service because that's what some guy on reddit said you should be doing...


That was my point. You can easily add validation checks to JWT. Just invalidate tokens issued before a certain time.


How do you do it without the service going into some sort of a database to check if there's a certain time for which all tokens older than it should be invalidated?


Well you don't ;-)

If you stick to OAuth and OIDC you have the option to validate the tokens against the userinfo and introspect endpoints, but that's, just another "database"


> The idea is that you have a disgruntled employee with a token that expires in 5-10 minutes.

Assuming the tokens issued to employees have an N minute lifetime stop replacing expired tokens for that employee N minutes before you do whatever it is that might disgruntle them enough to make them try to trash your systems?


Why doesn't the same argument apply when looking at other services? Can't they still do a lot of damage in that 5-10 minutes against those services?


You're not wrong.

I replied to a sibling comment. What I do is use the JWT from oauth or whatever sso, verify it, and log the user in as normal. Using the JWT as a replacement for a username/password.

I can invalidate the session or block the user as normal.


This is unnecessary - expiry time is a first-class capability of a JWT and records the time of expiry, not when it was created.


Yes, but if you want to invalidate say currently logged in users, before the tokens expire.


I suppose, but generally the solution is a refresh token with the JWT having a very short expiry to the point where adding a timestamp would be superfluous.

IMO the only flexible way is a denylist of tokens or JTI which adds the burden of more infrastructure.

My take is to use an opaque token unless you are using OAuth and paying someone else for all these extra capabilities, at which point things become easy.

ex. If you are using Firebase Auth or Auth0, they manage the token revocation for you so the problem sort of melts away and you are left with only benefits of JWT. Just have to blacklist the JTI claim and it'll automatically be denied in the future.


The only argument they elaborate on is still easily addressed with a validation of the token against a database on every request, and it's hardly a drawback compared to other methods if the other methods do the same thing.


Well, it's a drawback because if you're doing the validation anyway, why bother with the added complexity of the JWT in the first place? You've removed the primary advantage.


I'm not sure why JWTs are considered complex. In C# I generate tokens with a couple lines of code. Sure you can get fancy with the claims, but it's not required if you're going to access the DB anyways on authenticated requests.


You're right, they aren't, particularly. Just moreso than alternatives - especially if

> it's not required if you're going to access the DB anyways on authenticated requests.

then what's the point? You're just re-inventing bearer tokens.


I can see one massive advantage is that you could disable the db access/verification for service-to-service communication, since revoking an internal service token is exceptionally rare (and if it occurs, you have much bigger problems to worry about).


Which is exactly the kind of valid use case already presented in OP.


Can't you keep a whitelist/blacklist of tokens in a memory cache like redis/memcached and go from there? As far as I know that is the standard practice to invalidate non expired sessions tokens.


Yes but if each microservice needs to check a cache on each request, then why use jwt at all, you could just save a classic session (with random token) in that cache as well


The redis cache with invalidated tokens will be much smaller than storing all sessions.

And you can expire the invalidated keys faster (set the invalidated key expiration to the expiration of the JWT)

Not many people revoke sessions, but a lot of people create sessions. Much more efficient to only store and check revocations. The rest can be stateless.


I don't think that biggest issue with central auth database is its size.


It depends on your scale, but even at small scale, your central auth database is something that needs to be highly available because your whole system is down otherwise.

Obviously the situation is the same when managing a token blacklist if you truly have a hard requirement that sessions be instantly invalidated at a specific point, but there's a good chance you don't. Maybe it's OK to presume signed tokens are still good for some amount of time if your blacklist server is unreachable, or maybe waiting until the JWT expires after 60 minutes is too long, but a one or two minute delay is acceptable. Or maybe you only check the blacklist for high-risk API requests.

It's not ideal. Invalidation is definitely a weakness of JWTs, but there's still a lot of value in baseline statelessness.


Why not let the API Gateway check it?


"Yes but then ... why use jwt at all, you could just save a classic session (with random token) in that cache as well".

JWTs add complexity and have zero benefit. The author never said it can't be made to work, obviously it can. It's just completely pointless in most applications.


As explained in the article, if that's the case then you can't really trust the JWT anymore only for it's cryptographic signature and you rely on an internal store entry that makes the token valid / invalid.

This makes no benefits as to bearer token or any random string that the server "knows" is a valid authenticated request via internal store, like a DB.


But then you introduce a database-like dependency that potentially every micro service will need access to.


Yes, that comes with its own caveats


That's discussed in the article in the second paragraph under "Problems with JWT". If you're keeping a cache at each server node then you might as well just use bearer tokens.


Yes, and there is even a JWT field to hold the unique identifier for that token - https://www.rfc-editor.org/rfc/rfc7519#section-4.1.7 .

Makes it easy to track issued tokens and revoke them too.


why not just use a session token at that point?


Because you're only storing the invalidated tokens and you're only storing them for the length of the token's lifetime (expire the redis key when the JWT expires)

So instead of storing all session tokens indefinitely, you only store invalidated tokens for a short period of time.


Since when were session tokens ever a bottleneck? That's a problem I've never heard of on any scale. Yet again, JWT seems to be trying to solve a non-problem. Session tokens don't even have to be stored indefinitely. If a user isn't active for a period of time (even 30 days let's say) then the session token can be removed. Or, if memory truly was a problem for session tokens (not sure why honestly), transition tokens to storage after x amount of time and bring them back into memory when the user is active again.


> Since when were session tokens ever a bottleneck?

JWT allows for a user to authnz with a third party trusted by the second party. An example of this is HL7 FHIR SMART app launch, where an outside web application (2nd party) is opened from within an electronic medical records system (3rd party).

http://hl7.org/fhir/smart-app-launch/index.html


Here in Norway, the gov't is using it the same role[1], where they have a single agency (Digdir) handling authorization, so that the other agencies don't have to deal with that and can just implement their APIs.

[1]: https://docs.digdir.no/docs/Maskinporten/maskinporten_summar...


I simply explained the efficiency difference of the two setups.

You're storing and checking a smaller subset when doing stateless + invalidation cache.

Whether or not you need that memory optimization is up to you and your machines.

Personally it's about the same effort to implement and I prefer stateless + simple redis cache for invalidations.


Well, on the other hand, you will have to distribute that revocation list somehow, what does add some complexity to your code.

If you are already distributing lists around your application servers (that impacts the format and top performance of the redis cache), then yes, it's quite simple.


Redis is pretty easy to distribute out of the box.

And most likely you'll want a simple k/v distributed cache for other things so it's no extra work.


But then you can just put your session data in Redis. You're eating the latency to Redis either way.


It's quite common. If you have an outage where your sessions are cleared, you can hit issues where all your users DDOS your auth service. If you persist the sessions you have the same problem except they DDOS the DB.

If you use JWT, users that get a new token are free to use your app without any further timeouts and reduce the load on the auth service. With a session system, the auth service reduces performance on the entire app.


> If you have an outage where your sessions are cleared

Why would that even be allowed to happen in the first place? Sessions should always have some level of redundancy even if the primary retrieval is done in-memory. An outage would imply that an auth service is simply offline. Loss of sessions is a sign of catastrophic failure and possibly inadequate architecture.

> you can hit issues where all your users DDOS your auth service

Even in a case of a breach where all sessions must be cleared, JWT is one potential solution to mitigate DDOS. The others are to not have client-side code that blindly DDOSes your server and to have some level of DDOS protection in front of the rest of your architecture.

Moreover, even if you solve this problem for authentication, there's no obvious reason why you can't end up in a similar situation if some other service in your architecture is down. With JWT, all you've done is take the one service that is fundamentally one of the least complicated (storing hashes in memory) and treated it as if it's a critical point of failure. Sure, it can be, but auth is also one of the easiest things to horizontally scale, distribute, and synchronize at a relatively low cost. In JWT world, you've gone from storing even just random session numbers and now have to manage encryption keys, TTL, and possibly invalidation records (nearly defeating the entire purpose). That leaves even more room for things to go wrong.

> If you persist the sessions you have the same problem except they DDOS the DB.

Again, so what? You're supposed to do things to mitigate DDOS on your services regardless of whether you're using JWT. If your auth service can be that easily DDOSed (presuming this is an average website and not Facebook scale), then it would only take some coordinated enemies with a bone to pick to DDOS you without even using legit JWTs.


>Why would that even be allowed to happen in the first place?

Not just failure, you could also see this with just big spikes in traffic like a release day.

Either way, sounds like you're convinced the cases are common enough that mitigations should be table stakes.


This "the invalidated JWTs list is smaller" argument really doesn't hold water. Size of cache is a red herring (unless you are facebook or something). 99.99% of the time the most important variables are simplicity and security, surface area for dev errors or attacks. Smaller invalid cache fails on those fronts and only is superior in ways that don't actually matter.


I answered those talking points here:

https://news.ycombinator.com/item?id=33020993

I disagree that stateful based sessions are simpler.

I disagree size of cache is a red herring.

You don't have to be FB to want to optimize your memory usage.

In the end I prefer the more efficient (mostly stateless) solution and it's really easy to implement.

But to each their own of course.


why is a blog post that directly praises its own product for short reasons on the homepage?


Because the content is also interesting, provocative, and 100% correct. Why are you attacking the character of the witness instead of the substance of the testimony?


why is this not the top comment...


The JWT is irrevocable by design, which is why it's time-boxed to a few minutes and not a few days. The refresh token is meant to be revocable. You the provider would store that in a database and remove it or mark it invalid when you need to shut the user out. So no, it is not simply moving the original problem around without solving it.

Most software devs using cookies would likely struggle to revoke an individual cookie - you can certainly architect that in, but if you're like most of us, nobody thought about it early on and now it's kinda problematic to retrofit. Same reasoning: "Well, it will expire on its own soon enough." JWT's actually make it easier to guarantee expiration since it's part of the token itself instead of being reliant on server-side mechanisms.

It's definitely a pain in the butt to juggle refresh tokens & access tokens as a client-side dev, and almost everyone's API docs are ambiguous and confusing about token lifetimes (refresh tokens often have lifetimes too). But that's another issue.


refresh tokens are a workaround for a problem that wouldn't exist if you chose not to use a JWT.


Again: If we decide to use cookies instead, we also need to architect in a Cookie Revoker to fix the same problem: That means tracking cookies against user accounts on the server side. I'm sure some folks have done this, but I don't know of any common frameworks that actually support it.


Or you can develop like it's 1999 and simply remove the session from lookup.


I'm always reminded of this seminal article by our very own @tptacek: https://fly.io/blog/api-tokens-a-tedious-survey/


I don't quite understand the need for access and session token as a mechanism of expiring sessions. Can we create a signed JWT with the expiration in the body itself? Since it is signed the expiry cannot be tampered with.


If you mean access and refresh tokens - both usually have a lifetime in the signed payload.

Your access/ID token is short-lived (~5 minutes). This token is trusted without confirming if the user still has access.

Your refresh token has a longer lifetime (hours to months) and can be used to trade for another access token (and a new refresh token, invalidating the old one), but every time you do this trade your authentication server can also check if the user still exists, is not banned, has not signed out and still has the same claims (username, email, groups...) and either not issue a new token or a token with different claims.

There are proxy servers that will do this entire thing in the background for you and hand you the claims of the current access token in HTTP headers.


If there is an expiration in the JWT, logging off is still an issue. You can't log out until the JWT expires.


In my experience, a working strategy for handling signout or revocation for statically verifyable tokens like JWT is straightforward:

- Clear client side state where you can. - Write signed out/expired tokens to something with a cheap heavy read/eventual consistency model - Fail to signed in if unavailable - Acknowledge that you are gaining latency/availability/ lower costs by trading some precision

I am aware of a very large website most folks use every day that did this for more than a decade and it worked fine.


great idea! I'm using JWT in one of my projects and still unsure how to fix the irrevocability of JWT while keeping them stateless. But this seems like a nice intermediate solution


Lack of invalidation ("out of the box") is indeed a weakness of JWT.

However, I think in some scenarios it doesn't matter. For example if all the user data is encrypted, an attacker can retrieve it from the server using the JWT but not decrypt it - how useless!

Besides, when it comes to criticisms like this, I would gladly like to be pointed to real-life incidents where JWT as the security-bottleneck was fatally exploited.


As an exercise I started experimenting with JWT after a colleague told me how great they were.

So i tried to build a simple CRUD webside with authentication using JWT just to practice. The section "Problems with JWT" perfectly explains the issues I had. I think a way too many people just pick up the next random fad and without thinking they start using it.


Great article. I was going to start finally using jwt:s but this helps me not to. Currently using sessions (fast & easy) and if not found, then cookie&db-login. Just after migrating the server found out that saving the sessions manually to database was great. That allowed all users to stay logged in while the server changed.


Disclosure, I'm the author:

If you're looking for something different from JWT, allow me to suggest Coze.

https://github.com/Cyphrme/Coze


I think the only two ways to use JWT tokens is:

1) you have some kind of api gateway, it would validate the session, issue JWT/PASETO that lives a minute or so, downstream services would use that token to talk to complete the request.

2) When you need something like AWS S3 presigned URL.

Everything else is just an opaque session token with extra steps.


>Issue solved, right? Well, kind of, except you've just reintroduced a bearer token, because that's exactly what the refresh token is.

Entirely missed the point that refresh tokens will not work when your app (or your user) has told the IdP to sign out - unless you requested offline access.


Thankfully, recent events have changed the expansion of JWT in my brain's cache: when I first read this I thought "James Webb Telescope".

Not being a fan of JWT authentication I was doubly relieved when I clicked through.


This is a good overview, but I have yet to hear a compelling reason to use JWT.

String session tokens generated by a CSPRNG and stored in a cookie work fine for every use-case I can think of. What specifically is the argument for JWTs?


We have single SPA as front for multiple backend apps (I wouldn't call them micro services - each is responsible for pretty big part of the business and maintained by separate team). There's single identity provider responsible for token generation, sign in, refreshing the token every few minutes.

It's easier to have for eg. userId embedded inside signed token. Nobody has to call identity provider, they don't even care from where did the tokens came from.

Our threat model for this software doesn't care too much if token is valid for some seconds after logout. At this point user was probably MITMed or lost control over hardware and has much bigger issues than us.

Could we do this differently? Probably yes. Would there be a positive impact for the company if we dedicated time to this? Very doubtful.

JWT is only a tool. Sometimes it fits, sometimes not. No reason to get religious over this.


I was significantly creeped out that my state was issuing COVID passports with JWT.

Especially when I signed the opt out form requesting to not be placed on the database.

Especially 2.0, when I called the database, and was told that my records could not be deleted.


How is issuing a JWT implicitly mean that they are storing it in a database? Couldn't they have just... not stored the data... but can still validate it?


Exactly this. They just need to validate the JWT signature against a JSON Web Key Set (JWKS). There's no need to store the data.


I was significantly creeped out that my state was issuing COVID passports.


Are you aware governments have been tracking vaccines and issuing vaccine cards for long long before COVID happened?


This is actually a decent use of JWTs (and generally I'm in the JWT=='bad' camp). These things don't ever need to be revoked and issuing a JWT means they don't have to store it in their own database. Also 3rd parties checking that passport don't have to implement any kind of database lookup/integration with state APIs. Pretty solid architectural choice IMO.


Exactly. JWTs assert claims.

If your claim is not durable enough for the protocol's expiration setting (e.g. authorization, in some cases), then don't abuse JWTs. Simple!


This is really nice way of putting this. Logging in/out is authorization (and authn) so generally if you want to revoke access (log people out), JWTs are possibly the wrong choice (the thing people use JWTs for 99% of the time)


Oh yeah, exactly! Perfect application. I hate it.

Makes it really easy to scatter low budget devices across the state that require no Internet connection, and gives a red light/green light on whether you're allowed to go shopping or be out in public that day.

All the infrastructure is right there in place. Didn't get that far.

This time.


Businesses have the right to refuse service for any reason that is non-discriminatory. That's a good thing even if you don't agree with their reasons. I know some people do, but I don't count "you can't come in here if you can't prove you aren't going to kill me by negligently infecting me and my customers with covid" as discriminatory. Anyway covid is over so let's all move on.


Most of the "infrastructure" is the store owners and etc complying with the passport system. The technical part is irrelevant.

Anyway, all of that "infrastructure" is people making judgments. It will enable a police state if the people think they should enable it.


And you don't need all this tech for a police state anyway. Police states have been around for a long long time. Doomsday worries about hypothetical uses of tech seem yeah very hypothetical. I agree we should fight govt encroachment on our rights, but if you do that with too paranoid of a lens, then we won't have nice things (USA 2022 in a nutshell). How about, let's fight govt oppression when/where they are actually oppressing us.


> These things don't ever need to be revoked

What would the procedure that can guarantee no mistaken passport will ever get signed look like?

Of course you need some form of revocation, otherwise the first bad apple will spoil the system.


They used "JWTs" as a convience, Smart Health Cards have none of the issues here. Their issuance doesnt require a database either, thats just your healthcare provider.


JWT and SMART Health Cards are similar under the hood. The T part of JWT refers to a Token, which in JWT is a standardized set of claims. Instead of T, SMART Health Cards use S, or Serialization. Instead of a token, the JWS serializes FHIR demographic and necessary medical information about the card bearer. SHC has all the same invalidation and key issue/revoke challenges as JWT.

When the issuer is ready to generate a Health Card, the issuer creates a FHIR payload and packs it into a corresponding Health Card VC (or Health Card Set).

The VC structure (scaffold) is shown in the following example. The Health Cards framework serializes VCs using the compact JWS serialization, where the payload is a compressed set of JWT claims (see Appendix 3 of RFC7515 for an example using ECDSA P-256 SHA-256, as required by this specification). Specific encoding choices ensure compatibility with standard JWT claims, as described at https://www.w3.org/TR/vc-data-model/#jwt-encoding.

https://spec.smarthealth.cards/


your vaccination record does not need to be invalidated, so there is no issue.

it's a standard format for transmitting data signed by an entity.


Apart from the issue of not being able to log out there is the following list in the article:

"We've ignored things like increased attack surface due to complexity of JSON parsing/validation, misconfiguring JWT libraries to allow no signature, leaking data to users because you thought JWTs were encrypted (they're usually not by default), and poor implementation of API clients that don't properly re-run requests that failed due to expired JWTs. The list goes on, and on, and on ..."

All of these sound like usability issues. Not to downplay them but are there more serious issues with JWTs?


It's like C. The language is fine, it just allows you to do bad things too easily. The same is true with JWTs. The idea is fine, but allows you to do bad things too easily.

The no signature bit was a huge flaw in almost all JWT libraries across languages a few years ago.

There are better schemes out there.


I just use both. Use randomly generated tokens that you use to look up user information in your database, but also sign that token and present it as a JWT.


Hmmm...put the expiration datetime in the token and have your middleware handle it (verify and check for expiration)?

Failing to see a problem.


That's already done.

The problem is that apart from expiration, the token is never invalidated, for example when the user logs out. An attacker who stole the JWT is still logged into the victim's account until expiration.


Cookies are quite problematic these days


Care to elaborate on that?


If people have cookies disabled, or when you try to load content in a semi-sandboxed manner by using IFRAMEs than you can't use cookies, localStorage, sessionStorage etc.


You mean first-party session cookies disabled?

How many people do that? Or better, how many people do that and still expect anything to work? (Anyway, iframes don't break those in any way.)


Probably means "socially problematic" because there is real orthodoxy here. If you speak badly JWTs in an interview... that's probably problematic.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: