Biscuit authorization
biscuitsec.org
biscuitsec.org
Revocation is essentially the Achilles heel of JWTs - you want to have completely stateless bearer tokens, but if you need to check for revocations, it means you have to do some sort of lookup against a revocation table, and at that point you really lose the stateless nature that you wanted in the first place. There are some tricks to make this more tenable, but it's still a big reason why some folks say "don't use JWTs for end-user auth".
Would greatly appreciate details on how Biscuit implements this with their "revocation IDs".
Edit: Nevermind, I found it: https://www.biscuitsec.org/docs/why-biscuit/ - and it turns out revocation is a "non goal". And then in the linked page about how to implement revocations, they basically do exactly what I do for JWTs: have a revocation list, but then revocations can fall off that list once they're past the expiration date anyway.
So, not to be too harsh, but why would I use this over JWTs? There is no "magic" getting around the "well, they're kinda stateless" problem. To be honest I don't really care about some of the other tangential benefits (after all, you can stick whatever you want on a JWT and interpret it however you like) if it still has by far the same glaring pain point associated with any bearer token system.
Why? Wouldn't a developer prefer to implement this inside its application logic anyway?
Edit: I think I figured it out myself: you're likely targeting the case where someone with a certain authorization wants to give someone else a weaker form of that authorization (attenuation).
By the nature of the problem you have to store some kind of a list of tokens. Either a black list as with JWTs or a white list as with classic session tokens. There is no way around it. This makes both approaches practically the same.
One can argue that the black list will in general be shorter than the white list, but in a case of a serious attack, would it really be so?
I am afraid that the community will now abandon JWTs and move to biscuits, macaroons, buns and meringues as they did with classic session tokens, throwing away the tools and security practices.
A place I worked at recently insisted on JWTs despite being a monolith and calling the database on every API endpoint. I still don't get why.
Protecting your database from even being queried when the request is unauthorized reduces your attack surface enormously.
No need to reinvision the rest of the auth flow if you just want to add hashing to reduce DB load.
I've even seen a few cases of Stateful JWTs, where the JWT contained a session ID and everything else was in the DB. Of course, this approach manages to be both an overkill and a security and inferior to the just-use-an-HMAC approach at the same time.
I guess DDOS attacks - if we're checking the session ID on every request then that's a potential attack if you just make up uuids and throw them at the server.
But JWT's themselves are an attack vector, surely? If there's any mistake in the encryption (or any vulnerability in the libraries used) then this is a door open very wide indeed.
The beauty of session IDs is the simplicity of it - very few moving parts, so very few opportunities for mischief. JWTs seem to be the opposite: lots of moving parts, lots of opportunities.
Comparing to session IDs, you have no way to know if an unique id is bogus or not. You have to check from a list, be it a cache or a database. This limits the scalability of the solution. I'm not an expert, but AFAIK JWT verifier can be stationed on the edge of the application network, and I have not checked this but I suspect they can even make a hardware solution for those kind of activity. That's definitely a big reduction of attack surface in terms of DDOS.
IMO JWT doesn't have that many moving parts. Encryption parameters are handled by libraries according to tested standards. The only real thing you need to do is to keep your private key safe.
So you have to check a signature, right? Maybe this is what the other commenter meant when mentioned encryption.
Session IDs don't have any of these problems. It does mean you have to do a database lookup on every request, and that can cause problems with scale, but if you have to hit the database anyway for other reasons then you have that problem anyway and have to solve it for those other reasons.
Encrypted session tokens with embedded expiry will serve the needs of 99% of applications/services.
I cited this as one example of that surface area that led to serious vulnerabilities. Most people don’t need multiple ways to encrypt their data, and certainly not a ‘no encryption’ option. Each added option adds more ways to mess things up.
Encrypted session cookies are a proven solution, widely used, and easy to implement yourself using only core libraries (http, crypto, JSON) without introducing security flaws IFF you are an experienced programmer and don’t deviate from the norm.
JWTs are complex. Too complex to implement yourself, so you need to introduce dependencies. Dependencies are usually huge, and each line of code introduces risk (even if that line is for a configuration you don’t use!). Using JWTs at all is far more risky, even if you are an experienced programmer.
If JWTs provides immense benefits over session tokens for your use case, that risk might be worth it. However, for most web apps, session tokens are good enough.
I much prefer sticking with something simple and easy to comprehend, with fewer potential footguns, and revisiting the decision when we have enough traffic that it could be a concern. Just putting the tokens in Redis can go pretty damn far.
The reason you avoid the db query isn't about trying to keep the db from being overloaded, it's that you can handle the request far away from the DB without needing to wait for the latency of hitting the DB.
Which is also still a complete premature and unnecessary optimization for most people. And then someone realizes their use case requires revocation so they add a DB check anyways.
For myself I use short lived JWTs with a refresh token that I can invalidate. Yes, this means if there is a breach you have wait the refresh period but honestly I’ve never seen someone move faster than my refresh time to invalidate a session (on the rare cases I’ve ever seen it done professionally) so I’m not very concerned about the potential hole. It’s just not in my threat model and I have to say I question some people who say it’s in theirs.
I’m not saying there isn’t a use-case for immediate revocation but that a lot of people who talk about it don’t actually need it and/or have no good method for using it. Unless you have automated systems that revoke a token then I fail to see how 5 more minutes after a request worms its way through support is the end of the world. The damage is either already done or 5 more minutes isn’t going to be the end of the world (again, by this point it’s been going on for hours if not days/weeks).
Even if I wanted immediate revocations I’d just use an ID in a JWT and compare against a list since I still find the benefits from having role/tenant in the auth token very valuable. Then all I need is something like memcached or DynamoDB to hold a TTY’d ID for 5-ish minutes. But like I said, my threat model doesn’t require that.
Still, any modern database system could handle these lookups for all but the largest sites, so this smells like premature optimization for the vast majority of projects out there.
I understand that it's not inherently more secure or performant, but it is convenient.
I do get wanting to keep it simple but with sessions I don't know what is more simple once your project starts to grow. Rolling your own auth can be a minefield especially if you want to span it across more than one service and most of the off the shelf solutions will push you towards OAuth/OIDC any way at which point JWTs are some kind of de-facto standard. The good thing is that once one has gotten accustomed to OAuth verbiage you don't really have to think about it any more.
I resisted microservice mania when it emerged, and now I feel validated that the pendulum has swung back.
At my clients, the typical case is that the "product" consist of an assembly of services where some have been written and some bought so being able to span homogenous access controlls over them is a nice feature of JWTs/OIDC :-)
JWTs are really nice because you can validate things like roles and permissions just by validating the token signature. And there are much better ways to implement revocation lists than "calling the database on every API endpoint" (if that's what you were referring to). Since revocation lists are usually very small (depending on the nature of your app), it's often possible to just replicate them to in-memory data structures on your servers.
I'd be surprised if there wasn't a similar strategy available to Biscuits.
That said, the documentation does this thing I see a lot where the author focuses entirely on things that are good. It repeats and clarifies things that are good (you can reduce the scopes of offline, but never increase the scope of tokens, and you can do it offline, without contacting servers. Any server can validate credentials because it uses public keys. And did we tell you about attenuating tokens?), but it kind of sweeps anything that is NOT a discriminator under a rug a bit. I don't think it's malicious. Creators get excited about the exciting bits of their project.
But there are a lot of details that may or may not be present, and if I'm evaluating your thing, I want to know which exist an which don't. Given a long-term token, can I create short-term, auto-expiring tokens? Is there some revocation mechanism? How do these things line up against JWT or OAuth? When should I prefer this, but also when should I NOT prefer this?
Still, I like a lot about the documentation. I love how it's not afraid to get into the weeds about EXACTLY what it does without losing the clarity. It's really easy to follow when it gets technical, which is rare.
> Given a long-term token, can I create short-term, auto-expiring tokens?
yes, with attenuation: https://doc.biscuitsec.org/recipes/common-patterns#expiratio...
> Is there some revocation mechanism?
In a token, each block has a revocation id, so if you revoke a token, all the tokens derived from that one will be revoked as well. As for how to handle those revocation ids, it's the same strategies as in other systems, reintroducing some state to check revocation lists: https://www.biscuitsec.org/docs/guides/revocation/
> How do these things line up against JWT or OAuth? When should I prefer this, but also when should I NOT prefer this?
JWT has a lot of footguns, especially in the way it handles the list of algorithms, and libraries tend to all go through the same set of mistakes (a lot of them are stable now though). JWT are good to transmit a small amount of data, and are overall well supported. JWTs do not enforce any authorization system, you have to add one yourself. Biscuit is very strict in how it deserializes and verifies tokens, so it avoids the usual JWT issues (partly by its spec, partly by an extensive test suite). Biscuits can be larger than JWTs, and can take longer to verify (one signature verification per block). Biscuit comes with its own authorization, based on a logic language, that can be both carried by the token and provided by the verifier. It has implementations in a lot of languages but is not as widely supported as JWT yet.
I'd say if you can live with JWTs containing a little data (like a sub to look up in DB) and a simple authz system like a RBAC, keep using it, that will work well. But if you can benefit from attenuation (ex: per request attenuation https://doc.biscuitsec.org/recipes/per-request-attenuation and delegation https://www.biscuitsec.org/docs/guides/microservices/ ), then look into Biscuit, it can help
I suspect the answer is: yes, via attenuation. :)
Like specifically what is the difference between check if and allow if?
Several years ago I was looking at doing a "blockchain policy DSL," where users could federate applications by subscribing to and caching parts of the chain, but there wasn't enough product juice in it to pursue (I've done a lot of IAM and digital identity work over the years). This biscuitsec authZ token has a lot of the right ideas. It's a hard problem that needs fewer solutions and just one viable product.
[0]: https://en.wikipedia.org/wiki/Macaroon [1]: https://en.wikipedia.org/wiki/Macaron
> the same as the English word "jot".
Only makes sense if your dialect has the cot-caught merger[0]
> The suggested pronunciation of JWT is the same as the English word "jot".
Replaced by RFC-7797[1] and RFC-8725[2], neither of which have the pronunciation suggestion. There was also an errata about the pronunciation line[3], which probably explains the mismatch.
[0]: https://www.rfc-editor.org/rfc/rfc7519 [1]: https://www.rfc-editor.org/rfc/rfc7797 [2]: https://www.rfc-editor.org/rfc/rfc8725 [3]: https://www.rfc-editor.org/errata/eid5648
That doesn't save any effort over "JSON web token".
You can say that offline attenuation and minimization doesn't matter; for a lot of applications, it probably doesn't. But you can't say OAuth2 has the same feature, and certainly not "had for yonks".
(Not sure why the images in that post are suddenly broken, will try to fix later).
This also reminds me that I need to finish off my own take on Biscuits/Macaroons that takes a completely different approach based on Diffie-Hellman. I call them Florentines.
* Secure, HTTPOnly cookies. This requires that your servers live on the same domain as the web page is being served from, but cookies will transparently be attached to any request and are unavailable to be exfiltrated.
* Some secrets can be stored outside of the browser javascript context so again can't be exfiltrated. This is pretty limited, but WebAuthn uses this for example.
* Keep the secrets server side in a session. You are still vulnerable to session riding, but not exfiltration of tokens.
https://backstage.forgerock.com/docs/am/7/oauth2-guide/oauth...
https://research.google/pubs/macaroons-cookies-with-contextu...
Macaroons are an abstract thing. They don't define an encoding format, nor any semantics or language for the caveats. So a lot of detail is left up to the implementer, which is why they're fairly hard to use. Their basic form is also built on symemtric keys and hmacs, so only the issuer can verify, which is a significant limitation in the real world.
AFAICT Biscuits define those (protobuf for encoding, a logic language for the caveats) and use public keys instead to allow other parties than the issuer (=holder of the root key) to verify them.
We're doing a big-ass blog post as we speak about how our Macaroon tokens work, and it covers how we worked around the issuer/verifier constraint (spoiler: a litefs-backed replicated isolated internal API) and we did basic stuff like encoding (spoiler: we defined a rigid struct schema, and just MsgPack'd it).
One of the things I think I have to say about Macaroons is that there's a sliding scale of how much you use it; I think there are some simple ideas in them that would be pretty neat in any application's authentication cookie scheme.
† Also one of a couple reasons, I think, that "standard Macaroons" and their associated libraries never went anywhere.
(And thanks! I’m still citing your Velvet Underground comparison.)
https://securitycryptographywhatever.com/2022/01/29/biscuits...
I'd recommend listening (or reading the transcripts) to anyone who's needs or wants to understand more about JWT and other tokens
(And if you're not, check out the other episodes still. That podcast is great, some of the episodes get really deep into the cryptography weeds but if I get trough them I still feel like I learned something :-) thanks for making it)
(Just kidding, ish, and though they're quite long they're not too long and I did read; would recommend.)
Second, I'm not sure (based on the talk) that I see how they had any of the problems that Macaroons solve.
Third, I think a lot of the trouble they ran into feels like generic "we built our own token system" stuff; for instance, they backed themselves into designing a refresh token system (all their Macaroons had a third-party caveat discharged by their central Rails app, which, without any extra context, seems like a weird choice); similarly, they ran into revocation problems, which they addressed by routing all their requests through their Rails application --- these are just standard "stateless token" problems that you'd run into with a custom JWT solution as well!
(They also issued a whole bunch of long-expiry stateless tokens, and then had to wait forever to transform them to their new tokens, which is a problem every token and stateless cookie implementer has had to deal with).
I feel like they kind of went "full Macaroon", exercising all the features described in the paper, some of which weren't good fits for their problems.
We wrote a survey of auth tokens (it's linked upthread) a year or so ago, and I mentioned this talk, and I remember at the time that it made a dent with me. But we've spent a year rolling out Macaroons and gotten real-world experience with them, and in the course of writing that up I re-watched the talk and was much less persuaded by it --- in particular, I found that much of it was less a critique of Macaroons and more just a good war story about rolling out any custom stateless token scheme.
"Portable - Biscuit is implemented in Rust, Haskell, Go, Java, WebAssembly, C..."
but nothing about Javascript, NodeJS and Python, which seem very common these days.
In the case of NodeJS they're using WebAssembly to run the compiled Rust code, which is an interesting approach.
If they're just going to wrap the rust implementation, I'm surprised they haven't offered Ruby FFI or PHP FFI wrappers too.
Why Biscuit instead of JWTs?
tl;dr, Biscuit (and Macaroons) can attenuate, JWTs can't.
Read: https://fly.io/blog/api-tokens-a-tedious-survey/
What does this mean? Let's say you're given a token to access System A and B whenever and however you want. You can create a new token from your token that only gives access to System A for the next 5 minutes.
Basically: attenuation gives a capability system.
Why Biscuit instead of Macaroons?
tl;dr Biscuits are easier to understand (and implement) than Macaroons.
Watch: https://www.youtube.com/watch?v=MZFv62qz8R
Macaroons are clunky and hard to work with in practice. That's probably not a feature you want in your choice of token technology.
Biscuits contain simple facts and clear policies written in Datalog.
Why NOT Biscuits?
Immaturity.
- AFAIK there is no compliance suite for all the Biscuit libraries linked https://www.biscuitsec.org/; and as such, unsurprisingly, there are corner case incompatibilities, especially in the authorization language parsers and Datalog expressions/operators.
- The Datalog runtime limits are user-defined. What is the maximum number of facts, application iterations, or even timeouts? That's up to you.
- Biscuit v2 (v3-4 in the proto) is the Official Latest Version. Some of the libraries support the older versions to varying degrees.. and the way that backwards compatibility is implemented gave me pause.
- Whole sections of the specification are `TODO`.
- The Datalog data types are bounded by the underlying protobuf definitions; and the libraries use the language native data types. There are casts and undefined behaviour at the extremes.
- Many of the libraries do little things like calling the equivalent of `Time.now()` internally. IMHO this sort thing should be stateless.
- There's heaps of tests, which is great! But, I didn't see any fuzz or property tests, which is less great.
In Summary...
Biscuits neatly package several simple and solid technologies: datalog, ed25519, protobufs. Once the ecosystem is mature, it'll be incredible.
thanks for the kind words!
as for the compliance suite, there is one: https://github.com/biscuit-auth/biscuit/tree/main/samples/cu... which has been instrumental when i implemented biscuit-haskell. It might not cover anything, but it is updated everytime a new corner-case is identified.
could you elaborate wrt backward compatibility? The current model is opt-in, so you only pay for the features you use (that allows incremental updates, with biscuit-rust and biscuit-haskell leading the pack).
I see two todos in the spec indeed, i'll have a go at filling them out (they are implied by the rest of the spec and are implemented consistently across the various libraries, so it's more of a "writing things down" issue. I do agree that the spec is not airtight as it is. The compliance suite is there for that as it disambiguates a lot of things.
Other points are about library specifics. I know biscuit-rust and biscuit-haskell very well, biscuit-java less so. It is a bit lagging since the company working on it has had trouble allocating time on it.
In any case, if you have issues with specific things in libraries, don't hesitate to ping maintainers or open issues to see what we can do. biscuit-rust and its derivatives are actively maintained. biscuit-haskell is rather stable (but lacks support for snapshots). biscuit-java and biscuit-go are in need of some love, but nothing undoable