Hacker News new | ask | show | jobs
by kaoD 980 days ago
As a side question (or rather, what I expected the central point of the article to be instead): how are you supposed to actually use JWT?

The point of JWT vs opaque tokens is that you can just inspect the token itself to derive permissions without hitting any sessions in DB, right?

This means we need a short-lived access token (5 min or so?) so that sessions are revoked in a reasonable time if the token is stolen somehow (this is already scary to me TBH, someone can still do horrific things in 5 mins of intrusion time, but that's another story). Now my question is... how do you even handle the refresh token then?

I understand that long-lived refresh tokens means you can actually go to the DB/microservice/whatever and check if the refresh token has been revoked (via log out for example) since they'll valid for much larger intervals, so you can afford the session lookup... But if a refresh token is long-lived, what is the difference from having an actual long-lived access token? You'll still be handing out access token for a while. I can't see the difference here.

What am I missing?

EDIT: I could see the point if the threat models for both were different (e.g. having access tokens in memory, refresh tokens in a super safe vault, like e.g. ChatGPT would need to do for actions) but having both refresh+access tokens in the same threat model seems like it does nothing to me.

9 comments

In a distributed system with a dedicated authentication service, you can have that authentication service validate every JWT at your infrastructures entry point, and then only allow requests with valid JWTs to go downstream.

Those downstream services can then perform all their authorisation checks using just the JWTs alone. They don’t need to reach out to the authentication service to validate them, or lookup permissions, that’s already happened upstream. If those downstream services need to call other services, they can just pass along the JWT, and it allows the next service to validate the request is performing an allowed operation, without having to contact the auth service, or perform user lookups etc to determine what’s a valid operation. It’s all encoded in the passed along JWT.

There’s also security advantages because the issuing and primary validation of JWTs can be isolated to a single well vetted service, that has very limited connectivity, and every other service uses JWT validation with public keys to perform permission checking, rather than endless user auth lookups. Those service would also have to contact your central auth service to get new JWTs issued, ensuring there’s only a single place in your infrastructure that holds the sensitive private signing keys, and has the ability to issue new tokens. Which means you have a single gatekeeper that determines what types of tokens other services can access, allowing you to enforce limits on those services, even if they don’t cooperate (maybe they’re owned by a different team). It also provides a central location to limiting the blast radius of compromises, you can mass invalidate tokens with cooperation from other services, and stop issuing new tokens to services that might be compromised.

Ultimately JWTs shouldn’t be used for “efficiency” or “performance”, but rather as a tool that allows you segregate sensitive authentication and authorisation infrastructure from all your other services, and prevent “normal” services from adding ad-hoc authentication and authorisation capabilities in way that’s hard to audit or control, without limiting their ability to enforce authorisation requirements.

You could use the access token for each request. The advantage is that it is a simpler approach, and does away with the 5-minutes restriction you refer to, as the logout/invalidation would be immediate and not in 0-X minutes where X is the access token life in minutes. The disadvantage is that serving each request will involve making a round-trip with the auth service. This means at the minimum a DB read for every request, but could also mean a call to a separate, (possibly a third-party) auth microservice, and with possible fraud-detection measures each request. Depending on your use-case, you can drastically reduce the number of calls made to the auth server/database by using JWTs (or any other "algorithmically verifiable" token). This improves performance and enables architectures where for example you have a single auth server globally but multiple "functional" edge servers close to your users to serve out requests as soon as possible.
You can revoke tokens before they expire by distributing a token revocation list to front-ends. This involves some extra work, but presumably, the list of unexpired but revoked tokens will normally be short.
I might be wrong about it, but my understanding is as follows:

You're getting refresh token using credentials (e.g. login/password). Refresh token is long-lived and basically allows you to use service without typing login/password every 5 minutes. How much refresh token should live depends on your security requirements to how long user can user your service without typing his password.

You're getting access token using refresh token. This allows server to check whether you've been banned or something like that. If you encode access permissions into access token, it also allows to adjust those permissions. So your access token lifetime is a balance between performance and security.

If your API already uses some kind of service key, then refresh token does not make much sense (may be it does to provide uniform implementation for web clients and service clients, but not from security perspective).

My opinion is that all those things are more complicated than necessary. You could use the same token both for access and refresh purposes (if token is outdated, check it using database and return new token, then client will use new token for subsequent requests).

I am no JWT expert, but I have found them useful for APIs rather than web apps accessed from the browser.

If the user is expected to use a programming language and hit the API many times a second, then even a short expiration and moderate refresh really help (might fully authenticate every 5 mins, which could be thousands of requests for some APIs).

But again, what's the difference between that and just having a moderately-long access token if you'll still be handing out access tokens for the lifetime of the refresh token?
Another comment mentioned it, but:

1. You can check for user changes when the refresh token is used and a new access token is requested (user deleted, permissions changed, access revoked)

2. Refreshes can go to a different endpoint, whereas access tokens could be used with services that don’t even have access to user auth services.

Edit: There is something to be said for ditching the refresh token and just re-authenticating when the access token expires. The benefit is that you don’t need to store the username/password in the client code to re-auth, and you might avoid some relatively expensive password checking on the server, but that is probably pretty minor.

> for the lifetime of the refresh token

Not guaranteed for the lifetime of refresh tokens, which is the case for access tokens.

> whats the difference?

A 5 min access token is valid for 5 minutes. A 5 minute refresh token can be revoked before its even used.

> The point of JWT vs opaque tokens is that you can just inspect the token itself to derive permissions without hitting any sessions in DB, right?

As I understand it, de-centralized verification isn't a necessary characteristic of a JWT. There are token constructions that make that a priority, however[0].

[0]: https://www.biscuitsec.org/

> The point of JWT vs opaque tokens is that you can just inspect the token itself to derive permissions without hitting any sessions in DB, right?

No. Focus on “token” instead of “JWT.” JWT is just a standard.

Are you saying tokens can't be opaque?
This article confuses so many things that at first I thought it was making a completely different point. But let's go:

JWT is a format for tokens, it's not a choice between JWT and opaque tokens, as JWT can be opaque and transparent tokens can be in another format.

JWT just happens to be a format with good support in javascript. As a format, it's quite unremarkable, but as a standard it's a complete piece of shit where footguns abound. Anyway, it's easy to work with, while the alternatives tend to have the opposite features, being incredibly hard to work with, but easier to use correctly then wrongly.

Transparent tokens (the ones everybody knows how to verify) have the advantage over opaque tokens that you pointed out (they are easy to distribute). On the other hand, they are hard to revoke, so people tend to use short-lived ones. And yes, it's common for an application to have a long-lived opaque token that they only use in an authentication service to get short-lived transparent ones (that they use everywhere).

Anyway, the article is about the infrastructure for supporting JWT in javascript.

Your refresh token does not need to be a JWT, it needs to be checked once in a blue moon and a server side round trip is OK
But I already pointed that out in my question. You didn't address my actual concern.
The access tokens go everywhere, to all services and are much more likely to be accidentally leaked / misappropriated. Refresh tokens are only used when talking (infrequently) to the access token minting service, so the scope of use is much much narrower.
So if I get this correctly, this is mostly useful for microservices and would be kinda pointless for a monolithic architecture?
Not even necessarily microservers. If you want a common authentication system used by lots of disparate services, token based Auth is a reasonable approach.

If you've just got one service endpoint, it doesn't buy you very much. Just mint a session token and be done with it.

I'm not making any judgement calls here in terms of how things are _actually_ done, or what the _actual_ risk exposure is, but...

The design philosophy seems to be, use short-lived security credential A (the jwt) for all of your requests, which (again, _theoretically_) risks its exposure, and have a Security Credential B that you keep very secure, and use that to create new instances of Security Credential A.

If SC-A were a cookie and SC-B were kept in a hardware store, then I think this might be logical, but where they're both basically cookies, it seems kind of crazy to me, too. Maybe there are some details in how the actual storage of the cookies varies, but I think it's Hopium all the way down.

The point is JWTs can be validated independently on the server, no DB lookup is required. In distributed systems, that's the main benefit - they don't all need to talk to the auth server, just have a certificate that can be used to validate JWTs. This means they can't be practically invalidated per user though.

By contrast, refresh tokens go to the auth server that can do whatever checks are necessary to make sure the user is still allowed to use the service. This would typically incur DB lookups and require more complex auth logic than simply validating "yeah, this JWT is legit and still in-date".

You're missing that JWT were a bad idea in the first place.

Oldish but still largely relevant: https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...