13 ms·
Show HN: PAST, a secure alternative to JWT
- MrCalifornian 9y agoWhy not propose changes to the RFC instead of creating a new standard?
- Lazare 9y agoThis looks promising, and it's from a very well respected security researcher.
- lvh 9y agoThis is great. Having versions instead of kitchen sinks, and having those versions get rid of the footguns, is exactly what fixes the cryptographic JWT perils. Note: you probably still just want a random key in a database. And revocation is still an issue. But if you’re absolutely sure you want to mint tokens...
- zeveb 9y ago> you probably still just want a random key in a database. In general, I think that this is the wrong approach, because that means adding a database round-trip (which in a large system is almost certainly a network round-trip) for each and every API call. Notably, if the entire system is secured in depth (which large systems should be), it means adding a network round-trip for every layer of the API (e.g. one for the frontend server to validate the user, then another for a backend server to validate the frontend server). Using a stateful token[0] replaces that network round trip with a public-key or hash verification, which is much faster. > revocation is still an issue I think that generally revocation concerns are a bit overblown. Even with a database lookup on every request, there is a (small) amount of time that one is willing to act on out-of-date information (after all, the user's access could be disabled immediately after the lookup, before the action is performed). Almost every business has some window in which it is willing to act on old information: better to make it explicit than to leave it implicit. I really like the long-lived stateless token-refresh token[1] and relatively short-lived stateful access token approach often seen in OAuth2 setups. E.g. an email provider might only bother refreshing a read-email access token every five minutes or so, but might wish to refresh a delete-email or send-email token more frequently (or require a database lookup each time). [0] When I write 'stateful token,' I mean a token which is full of state; confusingly, some folks calls this a 'stateless token,' because the relying systems do not need to store or consult state. [1] When I write 'stateless token,' I mean a token which carries no state and thus must be looked up in some form of database — a random key in a database would suffice.
- lvh 9y ago> In general, I think that this is the wrong approach, because that means adding a database round-trip (which in a large system is almost certainly a network round-trip) for each and every API call. In general, asking a database for set membership is not close to the slowest thing applications do. > Notably, if the entire system is secured in depth (which large systems should be), it means adding a network round-trip for every layer of the API (e.g. one for the frontend server to validate the user, then another for a backend server to validate the frontend server). Using a stateful token[0] replaces that network round trip with a public-key or hash verification, which is much faster. A handful of those set membership checks: still not the slowest thing applications do. (Also, I don't think it's a given that internal auth has to work the same way external auth does, for a bunch of reasons.) > I think that generally revocation concerns are a bit overblown. Without revocation, you can't log out of things, and you can't invalidate sessions after credential rotation. So unless you're saying tokens should be valid for some tiny number of seconds... in which case, it's questionable that you're buying a lot of performance for your drastically more complicated token scheme. > Even with a database lookup on every request, there is a (small) amount of time that one is willing to act on out-of-date information (after all, the user's access could be disabled immediately after the lookup, before the action is performed). Almost every business has some window in which it is willing to act on old information: better to make it explicit than to leave it implicit. Median response time is not comparable to usual token expiry times. If you do make them comparable, then you're making that DB check every time anyway. The entire point of the token is to do that less. You're right that it's implicit, but it's also "the fastest that it possibly ever could be", so that's not a bad kind of implicit. If you really desperately want to make the check faster, why is JWT better than a local token cache?
- zeveb 9y ago> In general, asking a database for set membership is not close to the slowest thing applications do. From Jeff Dean's list of numbers every programmer should know[0], a round trip within the same datacenter takes on the order of 500,000 ns; a main memory reference is on the order of 100 ns. How often will an application be making an order of magnitude more than 5,000 main memory references to service a request? Sometimes, sure. In our experience online validation completely dominates our workloads. > I don't think it's a given that internal auth has to work the same way external auth does, for a bunch of reasons. Oh, you're completely correct. It has upsides & downsides. > Without revocation, you can't log out of things, and you can't invalidate sessions after credential rotation. I pointed out that I like the use of stateless tokens, which can be revoked. In many cases 'log out' can just be destruction of the token. And in many cases it doesn't make sense to invalidate access just because credentials have rotated (in many more cases it does, which of course is perfectly supportable with stateless tokens). > So unless you're saying tokens should be valid for some tiny number of seconds. I'm not: I'm saying that in many cases there's no business need to assure revocation within $SMALLNUM seconds, and that the business costs of online validation utterly dominate the costs of running a service. > If you really desperately want to make the check faster, why is JWT better than a local token cache? There are only two hard things in computer science … cache invalidation is one of them. [1] https://gist.github.com/jboner/2841832 https://gist.github.com/jboner/2841832
- saganus 9y agoWhat is a use case for minting tokens and one for not minting? I've seen this referenced a few times before but I don't understand why minting tokens is bad. Also, isn't creating a random key as a token the same as minting one? or what is the correct context here for "minting tokens"? Thanks!
- lvh 9y agoWhen I say "minting a token", I mean taking some data (e.g. your user name), adding some metadata (audience, expiry), and performing some operation on it (signing, encryption, authenticating) such that a different party will accept it as-is. (This is like "minting" because if you have a plausible-looking coin people would just accept it without having to go ask the issuer if it's a real coin via serial number or whatever.) When you generate a random key, you have to go ask a trusted third party what the data associated with that key is (and, therefore, if it's still valid). Minting tokens is bad because it's drastically more complicated, has tons of failure modes, and most of the time you end up doing tons more database transactions anyway that are way more complicated than set membership (i.e. is this still a valid token?).
- saganus 9y agoI see. It makes sense. In this context then, what are your thoughts on minting tokens using Macaroons [0]? I ask because they seem to be of much lower complexity than JWTs since they just hash the data so no encryption algorithm negotiation or anything of the like, however they still meet your definition of minting a token. Obviously it would depend on a case-by-case basis to prefer macaroons over say a random token or vice versa, but in general is chaining HMACS still considered "drastically more complicated"? My uninformed opinion is that verifying a hash shouldn't be that bad but my security expertise is pretty much non-existent so it would be great to be schooled on this :) [0] - https://research.google.com/pubs/pub41892.html https://research.google.com/pubs/pub41892.html
- lvh 9y agoIf you're going to mint a token, macaroons are a great spec. But when designing a critical security system, the default should be the simplest possible thing, and DB lookup is still much simpler than macaroons. So, you have to have a big problem that macaroons solve first. In my experience, the cure is worse than the disease here.
- allyant 9y agoJust a small 'way forward' for those wanting to have the ability to revoke a JWT after I came up with a solution on my last project: A 'Gateway' - use OpenResty to verify the JWT ID stored in a redis cache using a proxy pass. When the Authentication service grants a JWT add its ID to this cache along with a way of identifying the user. That way the entire advantage/disadvantage of decentralised authentication is not fully weakened and OpenResty + Redis can be relatively fast.
- AndrewStephens 9y agoForgive my ignorance, but if you are going to all the trouble of storing the JWT id in a server side database for verification why don't you just store the JWTs' claims as well and just hand the client an opaque random id? Your gateway could do the lookup and supply the claims to your backend without the client knowing anything about it. You wouldn't even have to validate the claims, since they never leave your servers. The best part of using JWTs is that you can validate without a central database. If you need a central database for sessions anyway you might as well store the claims in it.
- allyant 9y agoGood solution! Openresty could create the JWT and add it to the forwarding headers, although the user would still need a cookie to maintain session with the proxy. Only disadvantage I could see would be performance.
- jwtfail 9y agoCongratulations, you’ve just invented session tokens. Don’t get me wrong: stateless tokens (like JWT) are terrible in part because they’re irrevocable, but then why bother reimplementing session tokens with JWT? Just use plain old session tokens instead.
- unscaled 9y agoThey are not irrevocable - just harder to revoke. If you want something that avoids storing all tokens, you can use a blacklist. You don't even have to check for revocation on any call - you could perfectly use short-lived (say 10 minute) access tokens and force frequent refresh using a refresh token and then only check the refresh token calls. Whether you want to use it or not is a matter of making the right trade-offs. Stateful tokens are simpler to implement on the surface, but you have to keep in mind that the database lookup itself could be vulnerable to timing attacks. Unfortunately, most database-based token implementations I've seen perform lookup based on the token string, instead of looking up the user and then checking all of the user's tokens, one by one. And if you're not a small startup and actually have to handle loads north of 10,000 QPS (some of us do), these stateful tokens become quite expensive.
- pinguinFromY 9y agoYou blacklist your tokens in a cache and that's all.
- e12e 9y agoSo, for a secure system, the blacklist cache/service becomes a single point of failure (see also certificate revocation lists, ssl/tls). I personally think renewable, short-lived tickets/tokens are a better evil - accept that a compromised session is valid for 10 minutes (5+worst-case/accepted clock drift). A long-lived certificate can encode authorization + but needs a short-lived ticket to be valid ("an I'd card that says three star general and today's pass phrase").
- tptacek 9y agoThis is better than JWT. In particular: the whole protocol is versioned, rather than serving as a metaprotocol that negotiates the underlying cryptography. If you speak "v2" of this protocol, and refuse to accept any other version, then you're getting a token that is basically just Nacl. My only nit --- apart from the "auth enc seal sign" names, which aren't coherent --- is why do public key at all? Yes, Nacl supports it, but that doesn't mean the token format does. What's the use case for it? Who's asking for it? Specifically who? The overwhelming majority of JWT implementations I see aren't public key (except for the fact that the format is negotiated and might be tricked into being that). Why not punt "seal" and "sign" into a "v3", when/if it's needed?
- CiPHPerCoder 9y ago> My only nit --- apart from the "auth enc seal sign" names, which aren't coherent --- is why do public key at all? > Why not punt "seal" and "sign" into a "v3", when/if it's needed? Would you be happier seeing something like this? - v1: HMAC-SHA2, AES-CTR+HMAC-SHA2 - v2: RSA and all its sins - v3: Libsodium crypto_aead_* - v4: Libsodium crypto_{box,sign}_* > What's the use case for it? Who's asking for it? Specifically who? The only use-case I'm aware of as of this morning is OAuth2 users who currently use JWT for access tokens. https://bshaffer.github.io/oauth2-server-php-docs/overview/jwt-access-tokens/ https://bshaffer.github.io/oauth2-server-php-docs/overview/j...
- unscaled 9y agoI would happily do away with v1 completely. In my unfortunate experience you're still giving developers enough rope to hang themselves with by choosing older ciphers just because they are well-known.
- unscaled 9y agoTo extend on that, I can give list the lessons I learned from actually running a similar scheme internally in production for a while now: 1. Give developers the minimum amount of knobs. If they need to choose between 'auth', 'enc', 'seal' and 'sign' it's still going to be confusing. In my case they can personally come to me and ask "Which version should I use? Which type should I use?", but it's not clear. I'm still undecided whether supporting an unencrypted authenticated token is useful, but 'enc' should ideally be the default, with users going a little bit more out of their way to do anything else. 2. Asymmetrically signed payloads with an expiry are this strange bird that looks like a duck, quacks like a duck but unfortunately _are not a duck_. They're certainly useful, but when I was calling them 'tokens', my users were confused whether they should use them as access tokens. It's great to have a simple and robust encoding format for packaging payloads with an Ed25519 signature, but it's better to clearly call it something other than 'token', or you'll end up with users deciding they should use asymmetric access tokens just because it makes the key more secure. 3. You want to have some mechanism to specify key ID or key version in the wrapper, because this allows you to do automatic key rotation gracefully. You can use the optional (AEAD) payload for that, but that wouldn't be entirely clear to the users how. Let's say you want to rotate the key every week, but your longest token has a TTL of 24 hours: you will need to have a window where you would support two different encryption keys. In other rotation scenarios (e.g. rotating every 24 hours with longest token TTL being 30 days) you can have many more encryption keys supported concurrently. You can iterate and try all possible keys of course, but having key ID/version is much cleaner.
- cheez 9y agoI'm not a security expert but when I looked into JWT I was terrified at how easy it was to screw up. Glad to see I'm not the only one.
- atonse 9y agoI have implemented JWT on my app but the library I used (Guardian, written in Elixir), only allows you to use HMAC-SHA512 by default. And I've left it that way. Should I still be worried? I get that JWT's algorithm flexibility is overall a bad thing, but if I only allow one, should I continue to worry?
- lvh 9y agoI haven't reviewed said library, so I'm taking your word for it that it's actually limited to that suite :-) The problems with JWT are more complicated than just negotiation, but you should be OK here. Here's why: - Some bugs are about negotiation, e.g. key material misuse between RSA and HMAC schemes. They don't affect you, because you don't negotiate. - Some bugs are about cryptographic implementation, such as not reusing nonces for ECDSA. They don't affect you, because _HMAC-SHA256_ doesn't have most of these problems. - Some bugs are about specification issues, such as non-mandatory aud (audience) and exp (expiry). Audience shouldn't be a problem for you, because the only audience is you and there's only one secret key, so you get automatic audience restriction via cryptographic binding. Expiry, well, that's on you. Why did you use JWT to begin with? (What does minting tokens buy you?)
- atonse 9y agoJust to clarify, it isn't limited to that suite, but it has a default whitelist of only allowing it. So I COULD change it, but I didn't. "Why did you use JWT to begin with? (What does minting tokens buy you?)" Absolutely no compelling reason apart from: - Never want to roll my own auth, and there were already libraries in elixir and ember to work with JWT, so easy to cobble together - The HS512 stuff seemed secure enough - Impression that JWT is generally where things are headed (although I made this decision 2 years ago) - I can embed some attributes in the token that can be read from the client side - I liked the idea of authenticating without using a database query. A lot of these things haven't borne out in the last two years. I still make database queries during auth, and I still retrieve user metadata from an API in my client side. So just to clarify, I'm not attached to it in some way (I never am, to technical decisions). I mainly want to know if I should prioritize moving away from it or not.
- tootie 9y agoI don't get it. We used to use something like a pipe-delimited string, then JWT, now PAST. Isn't the encryption doing all the work regardless of how the data is structured?
- lvh 9y agoGetting the encryption right is pretty tricky! How do you verify the encryption method for a message, for example? That's a real problem in JWT: that's how RSA privkeys leak. PAST solves this by not negotiating. How do you make sure nobody's doing nonce reuse in ECDSA? That's a real problem in JWT. PAST solves this by only having (v2) specify exactly how to do that. Just because this specifies a format, doens't mean it's just a format :)
- grogenaut 9y agoSure but to the GP's question, the format of json vs pipe delimted strings is not the problem, it's WHAT you put in the Json or string and what it allows (eg configuration) that is the problem, correct? GP: Basically PAST is just limiting your options down to things we think are secure combinations and eliminating things we know are insecure. However it does this with WHAT it puts in the JSON, not that it's JSON vs anything else. As you said that's just serialization.
- CiPHPerCoder 9y agoThis might also be of interest: https://github.com/paragonie/past/tree/master/docs/02-PHP-Library#using-the-protocol-directly https://github.com/paragonie/past/tree/master/docs/02-PHP-Li...
- lvh 9y agoSort-of, but not necessarily. It's a lot easier to get the format right if you know there's an authenticator and you know what length it is and it's totally separate from whatever is coming next -- so you still want clear, out-of-band signaling for the real data. Once you have that format, you're right that the exact serialization doesn't matter. The extreme example of this is XML DSIG and XML canonicalization in general.
- Daycrawler 9y agoThis doesn't solve the criticism against JWT being used for sessions, which is one of the main point against JWT expressed in the very site linked at the top of the README.
- CiPHPerCoder 9y agoThere's nothing I can do at this layer that will stop people from using JWT/PAST/etc. as an attempt to build stateless session management systems for some ill-conceived "horizontal scalability" requirements, except maybe continue to tell people this is a bad idea and don't do that. The rest of the points (i.e. the problems with the JOSE standards) are what PAST seeks to solve. The "do not misuse" problem is more complicated, and if I were to add e.g. "do not use this for stateless sessions" at the top in big red letters, that will only tell developers "this is unsafe, keep using JWT instead".
- enobrev 9y ago> that will only tell developers "this is unsafe, keep using JWT instead". That's a good point. Maybe a header in the readme/docs like "Stateless Sessions", followed by "Using PAST/JWT/etc. for stateless sessions is a terrible idea, because kittens will die needlessly and painfully [ obviously using an actual summary of why ]. Don't just take my word for it, here are some resources explaining further..."
- treve 9y agoWhy is this a bad idea exactly? I'm still very interested in the idea of using stateless sessions. Is it just that it's hard to expire sessions server-side, or is there more to it?
- CiPHPerCoder 9y agoI've written about this at length here: https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba... (It's also the first link in the README for the project this Show HN is linking to, FWIW)
- idbehold 9y agoI do wish a different acronym had been chosen. When I search for "[language of choice] JWT" pretty much all results are relevant. But even if this new token schema takes off it will forever be a hassle to find relevant results for "[language of choice] PAST".
- CiPHPerCoder 9y agoI spent two weeks (my Christmas vacation) working on rough drafts for several problems I wanted to solve in 2018. PAST was one of items I listed. (The list is here: https://github.com/paragonie-scott/public-projects/issues/6 https://github.com/paragonie-scott/public-projects/issues/6) 99.9% of that time was spent trying to come up with a better name/acronym, without success. I decided to just give it a plain/obvious name until a better one surfaced.
- idbehold 9y agoVersioned Protocol Security Tokens: VPST Novice-Proof Security Tokens: NPST Just Verify Valid Tokens: JVVT Safer Security Tokens: SST Note: the first one is my only real suggestion, the rest are just for fun. And you are right, it is surprisingly difficult to come up with a good name.
- cdubzzz 9y agoMaybe just change "agnostic" to "neutral": PNST? "Independent" is probably better, but PIST may not be so great a choice (:
- irq-1 9y agoRefer to it with a version number: PAST2, PAST3, etc.. You could also drop the use mapping: change 'sign' to 'public-auth' and explain that it's a 'sign' operation (a.k.a. digital signatures)
- audiolion 9y agomy favourite is PAST4 tee-hee
- 9y ago
- waibelp 9y agoThis library is a great example of clean and beautiful php code out in the wild!
- partycoder 9y ago"A secure alternative". Citation needed.
- deleted 9y ago[deleted]
- lxe 9y agoI really don't get the whole "the spec supports choosing of algorithm, therefore the whole implementation is bad" If my server-side application sends a JWT with a "good" algorithm, and disallows any other alg's, wouldn't that prevent attacks? Why do we need a whole new implementation?
- treve 9y agoI think one of the issues is a very practical one. When everything is called JWT, it's hard for users to figure out whether it's a secure or insecure implementation. It's completely possible to do secure things with JWT, especially if you control every producer and consumer, but it's not guaranteed.
- jopsen 9y agoIt's completely possible to do insecure things with PAST. There is always going to be someone who hardcodes the root password into a public GitHub repo. The problem with JWT is IMO largely that things became too easy, so people coded without thinking. I'm not sure that's easy to prevent. I don't blame the JWT spec for mistakes that are so obvious.
- level 9y agoThe problem is that it's not idiot-proof. If someone doesn't understand why the algorithm is important, they might choose none. It's easy to say "they shouldn't be using JWTs if they don't know how to use them", but everyone starts somewhere, and everyone puts stupid bugs into production. JWT is safe, as long as it's setup correctly, but safe-by-default is a better option. That being said, I'm not going to swap out my JWTs with PASTs. I know what algorithm I'm using, why I'm using it, it is safe, and I'm verifying them properly.
- WorldMaker 9y agoThere's also the issue that the alg field is in an encoded section of the JWT payload and has to be base64-decoded and then JSON parsed. There have been buffer overrun and malicious JSON attacks on JWT. PAST at least moves that to a clear text prefix in the vx.scheme pattern. Theoretically, that doesn't even stop it from having a v0.none or some other dumb algorithm such as JWT allows in a bad version suite, but it does at least mitigate against decryption attacks.
- k__ 9y agoI had the impression that the main problem was that JWT was marketed as stateless and superior and then you were stuck with stolen tokens. How does PAST solve this? Is it even possible to get secure stateless auth?
- timwis 9y agoA common security issue I've seen with uses of JWT doesn't have to do with JWT itself but how it's used by front-end developers. It's commonly stored in localStorage instead of an HttpOnly cookie, which creates a cross-site scripting vulnerability. More details here: http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/ http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-fo... Shockingly, the advice I've seen to protect against this by folks like Auth0 is "keep your tokens expiration low" or not mention it at all. I don't imagine PAST gets around this, as it's more like misinformation around the storage mechanism, but I think it's worth mentioning in any "how to use" section about PAST or JWT.
- CiPHPerCoder 9y agoThis is somewhat orthogonal to the security goals I'm trying to tackle, but still very relevant to the ecosystem that currently uses JWT. So I've opened an issue to address it before v1.0.0 is tagged: https://github.com/paragonie/past/issues/14 https://github.com/paragonie/past/issues/14
- lvh 9y agoIf something is a HttpOnly cookie, and I get XSS, why can't I just hit your API as much as I like anyway?
- JimDabell 9y agoFor Python developers: I've started an implementation here: https://github.com/JimDabell/pypast https://github.com/JimDabell/pypast