7 ms·
The problem I've got with JWTs is that actually you can rarely (never, really in my experience?) assume anything in the JWT apart from user id are valid for a l
by martinald 1y ago
The problem I've got with JWTs is that actually you can rarely (never, really in my experience?) assume anything in the JWT apart from user id are valid for a long period of time.
For the most simple use case of an client auth state; you want to be able to revoke auth straight away if an account is compromised. This means you have to check the auth database for every request anyway, and you probably could have got whatever else was in the claim there quickly.
Same with roles; if you downgrade an admin user to a lower 'class' of user then you don't want it to take minutes to take effect.
So then all you are left with is a unified client id format, which is somewhat useful, but not really the 'promise' of JWTs (I feel?).
- paulddraper 1y agoI think you have a narrow view. For example, a magic link sent via email can have a substantial validity duration.
- ithkuil 1y agoYes but generally magic links are only used for authentication. So if you delete or downgrade the principal whoever uses that magic link to authenticate can only perform the operations that are associated to the principal and the check is performed after the magic link is verified, unless the magic link also used to carry auth claims
- paulddraper 1y agoYes, up-to-date permissions require centralized consistency. My point is…JWT can be used in a number of contexts.
- lo0dot0 1y agoClicking on links in emails is a security risk because they could be spam. I don't do that unless it's the only way to move forward and then I double check the url. Basically I only use it to sign up then never again if possible.
- hn_throwaway_99 1y ago> For the most simple use case of an client auth state; you want to be able to revoke auth straight away if an account is compromised. This means you have to check the auth database for every request anyway, and you probably could have got whatever else was in the claim there quickly. FWIW, I built a system previously that got around this "having to check the DB on every access to check for revocations" issue that worked quite well. Two important things to realize: 1. Revocations (or what is usually basically "explicit logout") is actually quite rare in a lot of user application patterns. E.g. for many web apps users very rarely explicitly logout. It's even rarer for mobile apps. 2. You only need to keep around a list of revocations for as long as your token expiry is. For example, if your token expiration is 30 mins, and you expire a user's tokens at noon, by 12:30 PM you can drop that revocation statement, because any tokens affected by that revocation would have expired anyway. Thus, if you have a relatively short token expiration (say, a half hour), the size of your token expiration list can almost always fit in memory. So what I built: 1. The interface to see if a token has expired is basically "getEarliestTokenIssuedAt(userId: string): Date" - essentially, what is the earliest possible issuance timestamp for a token for a particular user to be considered valid. So, revoking a user's previously issued tokens means just setting this date to Now(), then any token issued before that will be considered invalid. 2. I had a table in postgres that just stored the user ID and earliest valid token date. However, I used postgres' NOTIFY functionality to send a broadcast to all my servers whenever a row was added to this table. 3. My servers then just had what was a local copy of this table, but stored in memory. Again, remember that I could just drop entries that were older than the longest token expiration date, so this could fit in memory. On the off-chance that somehow the current revocation list couldn't fit in memory, I build something in the system that allowed it to essentially say "memory is full" which would cause it to make a call back to postgres', but again, that situation would naturally clear up after a few minutes if revocations went back down and the token expiration window passed. This sounds more complicated than it actually was. It has the benefits of: 1. Almost no statefulness, which was great for scalability. 2. Verifying a token could still always be done in memory, at least almost. Over a couple years of running the system I actually never hit a state when the in-memory revocation list got too big.
- vintermann 1y ago
- the_duke 1y agoActive user logouts, deletions, permission changes are rare, so the size of revocations lists is extremely small compared to number of tokens in existence. You can keep revocations in a very fast lookup system (eg broadcasts + in-memory store), combined with reasonably short token renewals, like 5-60 minutes. Massively cuts down the number of token validity checks, and makes the system tolerant to downtimes of the auth system. That's less relevant for basic apps where the auth data is in the same DB as all the other data, but that is rarely the case in larger systems.
- rafaelmn 1y agoAnd when building distributed systems a bunch of systems don't really care about those changes immediately anyway so the propagation delay is acceptable, or you can push the burden of refreshing to the client for privilege expansion cases, etc.
- CafeRacer 1y agoIs there a proposed rfc for revocation lists?
- pwlb 1y agoYou may have a look at this (still a Draft): https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/ https://datatracker.ietf.org/doc/draft-ietf-oauth-status-lis...
- unscaled 1y agoI don't think status lists solve the requirement for near-realtime revocations. The statuslist itself has a TTL and does not get re-loaded until that TTL expires. This is practically similar to the common practice of having a stateful refresh token and a stateless access token. The statuslist "ttl" claim is equivalent to the "exp" claim of the access token in that regard, and it comes with the same tradeoffs. You can have a lower TTL for statuslist, but that comes at the cost of higher frequency of high-latency network calls due to cache misses. The classic solution to avoid this (in the common case where you can fit the entire revocation list in memory) is to have a push-based or pub/sub-based mechanism for propagating revocations to token verifiers.
- KPGv2 1y ago[flagged]
- hn_throwaway_99 1y agoI don't understand why you seem to think JWTs can't be used for authorization, and the fact that you denigrate this as a "puppynoob" approach is telling, but not necessarily in the way you think it is. Many large systems with millions of users (e.g. Google's Firebase) store user claims in the token, and that can (and is) used to validate permissions.
- p_l 1y agoJWT can be used to store permission bits, but didn't it properly is very complex topic and usually works well only as "subsetting of claimed userid permission set" or "short lived token to be passed to third party to perform an action on behalf of". One can do also quite complex system based on taking multiple claims as a whole plus signature, but that's taste in my experience other than stuffing user info into token
- unscaled 1y agoJWT is basically just a signed JSON container format (JWS) with a couple of standard claims and recommended verification procedures. They can be used as a component of an authorization system but they would just be a small component of that. I think the question should be whether JWT is a good fit to play a part in authorization? If you just need to tack in a bunch of scopes which are configured per clients and are rarely changed, or if you need to track authentication method and time for sensitive operations ("amr" and "auth_time" claims in OIDC), JWT could probably do the job. But for more fine-grained RBAC (or any user-permission-based scheme), JWT is quite problematic. For one, you will need to revoke the access token every time permissions change. On certain type of systems this could happen quite often (e.g. every time somebody shares a folder with you), and you don't want to constantly log the user out, so all clients would have to have logic to automatically refresh the access token (with a permission-less refresh token) on revocation. The other option is pure bloat: The moment you go beyond a simple admin flag or a fixed set of roles, you're already on the expressway to a bloated 10kb token. In every system that support a technically unlimited amount of resources and roles, storing all ACLs in the token gets you very large tokens really fast.
- pbreit 1y agoIf a token is not a JWT is it really a “Bearer” token?
- detaro 1y agoyes, 100%
- formerly_proven 1y agoBearer token just means whoever has the token string has the associated capability - like bearer bonds. Unlike e.g. challenge-response or signature authentication.
- unscaled 1y ago"Bearer" and JWT are orthogonal. Tokens in other format or stateful formats can be bearer tokens, while JWTs can use non-bearer authentication methods. For instance, RFC 9449 (DPoP) describes an authentication method where you have to provide a PoP (based on JWS) in addition to an access token (which may or may not be JWT).
- sbergot 1y agoOauth defines bearers tokens without requesting them to be jwt.
- motorest 1y ago> For the most simple use case of an client auth state; you want to be able to revoke auth straight away if an account is compromised. This means you have to check the auth database for every request anyway, and you probably could have got whatever else was in the claim there quickly. I fail to see the relevance of your scenarios regarding JWTs. I mean, I get your frustration. However, none of it is related t JWTs. Take a moment to read what you wrote: if your account is compromised, the attacker started abusing credentials the moment he got them. The moment the attacker got a hold of valid credentials is not the moment you discovered the attack, let alone the moment you forced the compromised account to go through a global sign-off. This means that your scenario does not prevent abuse. You are revoking a token when it was already being abused. Also, as someone who implemented JWT-based access controls in resource servers, checking revocation lists is a basic scenario. It's very often implemented as a very basic and very fast endpoint that provides a list of JWT IDs. The resource server polls this endpoint to check for changes, and checks the list on every call as part of the JWT check. The time window between revoking a token and rejecting said token in a request is dictated by how frequent you poll the endpoint. Do you think, say, 1 second is too long? > Same with roles; if you downgrade an admin user to a lower 'class' of user then you don't want it to take minutes to take effect. It's the exact same scenario: you force a client to refresh it's access tokens, and you revoke which tokens were issued. Again, is 1 second too long? Also, nothing forces you to include roles in a JWT. OAuth2 doesn't. Nothing prevents your resource server from just using the jti to fetch roles from another service. Nevertheless, are you sure that service would be updated as fast or faster than a token revocation? > So then all you are left with is a unified client id format, which is somewhat useful, but not really the 'promise' of JWTs (I feel?). OAuth2 is just that. What's wrong with OAuth? Also, it seems you are completely missing the point of JWTs. Their whole shtick is that they allow resource servers do verify access tokens locally without being forced to consume external services. Token revocation and global sign-offs are often reported as gotchas, but given how infrequent these scenarios take place and how trivial they are to implement (periodically polling an endpoint hardly changes that.
- fifticon 1y agoI have a random idea regarding compromised tokens, which may not hold water. What if you put things like the client's IP address in the token? Then the server can reject (and mark for compromise) as soon as they receive any request from a different ip address? I realise this will also invalidate people who somehow roam between ip addressses, say DHCP/wireless in a larger building.
- spiffyk 1y agoMore importantly, this will invalidate everyone jumping between Wi-Fi and mobile data, so that might be unworkable for many.
- deeringc 1y agoEnterprise customers often have split tunnel VPNs or proxies (with PAC configs) where part of the traffic may go through a VPN and another part goes directly. So for example a customer admin might configure an app that does email and webRTC so that the real time traffic (media and the associated signalling) goes directly and the email traffic goes via some TLS intercepting proxy for some compliance reason or DLP. This can result in one application having multiple public IPs for different network requests, even while they are on one internal network (not even jumping between networks like you say). That isnt something that the application author can control, it's the customer admin that decides to do that.
- hn_throwaway_99 1y agoClient IP addresses change a lot more than you think, especially on mobile networks.
- alexdbird 1y agoYup. I worked for a media company that made IP part of the access token for media playback. An absolute PITA for the mobile app, and made (reliable) background downloads impossible on iOS. A bad idea. They went out of business.
- mooreds 1y agoThere are two in-use RFCs to make compromised tokens much harder to use by attackers. Neither use IP addresses, but both bind the token to the client using some form of cryptography. RFC 8705 section 3[0], binds tokens by adding a signature of a client certificate presented to the server doing authentication. Then any server receiving that token can check to see that the client certificate presented is the same (strictly speaking, hashes to the same value). This works great if you have client certs everywhere and can handle provisioning and revoking them. RFC 9449[1] is a more recent one that uses cryptographic primitives in the client to create proof of private key possessions. From the spec: > The main data structure introduced by this specification is a DPoP proof JWT that is sent as a header in an HTTP request, as described in detail below. A client uses a DPoP proof JWT to prove the possession of a private key corresponding to a certain public key. These standards are robust ways to ensure a client presenting a token is the client who obtained it. Note that both depend on other secrets (client cert, private key) being kept secure. 0: https://datatracker.ietf.org/doc/html/rfc8705 https://datatracker.ietf.org/doc/html/rfc8705 1: https://datatracker.ietf.org/doc/html/rfc9449 https://datatracker.ietf.org/doc/html/rfc9449
- edf13 1y agoIf it’s a major compromise you can simply roll out a new key… invalidating all current JWTs forcing a new login… you could also group signing keys by user type to further minimise the refreshes.
- mooreds 1y ago> you want to be able to revoke auth straight away if an account is compromised It really depends on the system. In my experience, there are tons of apps that want to be able to revoke access but weigh that against transparent re-authentication. OIDC handles both nicely with: * short access/id token lifetimes (seconds to minutes) * regular transparent refreshes of those tokens (using a refresh token that is good for days to months) This flexibility lets developers use the same technology for banks (with a shorter lifetime for both access/id tokens and refresh tokens) and consumer applications (with a short lifetime for access/id tokens and a longer lifetime for refresh tokens).
- afiori 1y agoImho the complexity and cost of having super short-lived access tokens is worse than eating up 1 more per-request db lookup
- paradox460 1y agoYou don't have to check the db every request. Just store a list of revoked tokens in a fast cache, like redis, with ttl longer than the longest token lifetime and reject/force reauth any token that matches