Hacker News new | past | comments | ask | show | jobs | submit login
Biscuit authorization (biscuitsec.org)
180 points by mooreds 7 months ago | hide | past | favorite | 112 comments



I was searching for a quick explainer of how revocation is implemented, but didn't find it (at least not quickly). Can anyone say how this works?

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.


(Biscuit author here) there is some support for revocation with the way revocation ids are implemented: there's one generated for each block of a token, so if you add the token's last block's revocation id to the revocation list, then all tokens derived from that one will be revoked as well. We outline the strategies to manage revocation in https://www.biscuitsec.org/docs/guides/revocation/ but as you said, there is no magic here, adding revocation reintroduces state in the system. As for why you would use this over JWTs: Biscuit avoids JWT's usual footguns, specifies an authorization language where checks can be carred by the token, it adds attenuation and third party blocks. You can build a lot more with those tokens


> specifies an authorization language where checks can be carried by the token

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).


It could also let your clients do proactive validation without rewriting too much code, seems like?


> Revocation is essentially the Achilles heel of JWTs

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 simpler, but less targeted approach that works in case you're not tracking token issuance by ID is to maintain a list of maps of entity (e.g user) ID to minimum issue time. This way you can revoke all earlier tokens for a user ID with a single record, and not have to keep track of issued tokens.


I've already seen one company adopt Paseto tokens as though this is some sort of magic bullet.


In the case of a serious attack, the blacklist should be every token. You can still handle this quite nicely with JWT by rotating the previous verification key. Depends on systems and configuration, this can be as easy as changing the HMAC private key or push a new RSA key to every verifier.


Off topic: I'm always confused by the passion to use JWTs where normal session uuid's would do just as well.

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.


JWTs let you reject unauthorized requests on their face, without having to even make a database call.

Protecting your database from even being queried when the request is unauthorized reduces your attack surface enormously.


So sign your UUIDs and combine them into “$UUID:$HASH” strings for the same benefit. Or a more structured JWT-like payload that still verifies auth against the DB (as opposed to carrying authorization within the token).

No need to reinvision the rest of the auth flow if you just want to add hashing to reduce DB load.


so ... recreate jwt?


If you're worried about DDoS (and also timing attacks, if you've got a B-Tree index), you can just append an HMAC to your random session ID.

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 don't get why that's a reduction in attack surface?

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.


JWT doesn't encrypt (by default) the payload. The header & payload is passed through base64 and appended with a hash to produce a JWT. JWT verifying doesn't require making API call, and is essentially a hash check. Any verifier with a public verification key can determine if the JWT is bogus by a quick hash check and reject the request right away.

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.


> Any verifier with a public verification key can determine if the JWT is bogus

So you have to check a signature, right? Maybe this is what the other commenter meant when mentioned encryption.


Yeah. The mechanics of JWTs are complex, involving maths that is easy to get wrong. Hopefully the implementation is based on a standard library and the author didn't roll their own crypto. Hopefully the library hasn't got any known vulnerabilities. Hopefully the implementation hasn't got any problems. Because the JWT actually contains sensitive information that is useful to an attacker, and if they can forge their own JWTs then the system is wide open; all the attacker needs to do is work out the username of an admin (or worse, the JWT itself tells the system if the user is an admin).

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.


So does every Rails and Django encrypted session token.


Sure. Just pushing back on the ‘you’re going to hit the database anyway’ argument. Having some kind of mechanism for eliminating obviously bogus requests is a good idea. Some sort of signed, expiring token is probably a good idea for that. Rolling your own expiry/signing mechanism is probably a bad idea. Correctly validated JWTs are an option (among many) for solving that problem.


How do you revoke an encrypted session token that doesn't need to hit the database to see if the request is authorized?


Include an expiry time in the encrypted part. The encryption, and the fact the token decrypts invalidly if not encrypted with the correct key, acts like a discount JWT.

Encrypted session tokens with embedded expiry will serve the needs of 99% of applications/services.


In that case why not use JWTs which do this for you? In your case you end up building a subset of JWTs yourself.


A subset of JWTs sounds great given the previous security vulnerabilities found in JWTs and the huge surface area they give you:

https://www.akto.io/blog/jwt-none-algorithm-test


This is not a huge surface area. I don't know why this keeps coming up, but, while a silly default, is incredibly straightforward to not configure / remove / test for.


JWTs are definitely a much larger surface area than simpler encrypted sessions storage and most people don’t need that.

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.


JWTs are complex.

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.


No idea why you're being downvoted. This is the truth.


These might predate JWTs.


Indeed, RFC 7519 dates from 2015 while both Rails and Django are older than that by a decade or more.


Revocation in this context almost always means "suspension of the validity of a token prior to expiration", so that's not really an answer.


Down below there is a link to an article by tptacek that says that these random session tokens are the simplest and most secure and you should use them if you are able to (which is true in a lot more cases than they are used)

https://fly.io/blog/api-tokens-a-tedious-survey/


Having had a lot of conversations on this topic in early-stage organizations, it's really common for developers to want to prematurely optimize and move away from random bearer tokens. Something feels "wrong" about them or something. Often we're talking about avoiding one db query per request in an application that is nowhere near any database-imposed bottlenecks.

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.


> Often we're talking about avoiding one db query per request in an application that is nowhere near any database-imposed bottlenecks.

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.


JWTs are nice because you can skip a DB call completely if it doesn’t validate, furthermore it provides a very convenient method for embedding a tiny bit of date (role and/or tenant id) for use both on the back and frontend.

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.


DynamoDB note: the TTL setting isn't timely. It'll remove your item when it gets around to it. You have to filter it on the get item call or client side. It's great for cleanup but useless as an expiration mechanism.


For sure, it’s a nice clean up eventually but I always filter TTY client side when using it.


It is cheaper to look up at the handful of recovered tokens than however many active sessions you have, assuming you have a substantial number of them, otherwise, it is a moot point, similar to microservices for websites with 3-4 digits traffic per hour.


While this is technically true, using an index for either lookup makes the lookup happen in O(log n)-ish time, so neither is slow, even if you have a large number of active sessions.


The issue is not individual lookup time, it is the aggregate cost of millions database lookup requests that you can avoid.


Ah, sorry I misunderstood what you were saying.

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 guess you could use token exchange[1] to get benefits of both.

[1]: https://www.rfc-editor.org/rfc/rfc8693.html


From a developer standpoint, I enjoy being able to look at the JWT and reason about its meaning without having direct access to the underlying session database, e.g. what user is this and when was it issued.

I understand that it's not inherently more secure or performant, but it is convenient.


It can be nice in a distributed app built with eventual consistency. Often ok if revocations take a short time to propagate but that's generally not true for session tokens.


Wouldn't using JWTs in this case permit for starting to chip away on the monolith and integrating side services in the authz offered by the core service?

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.


Sure, but that presupposes that breaking apart a monolith is the goal, or even that "integrating side services" is a positive improvement.

I resisted microservice mania when it emerged, and now I feel validated that the pendulum has swung back.


Nothing wrong about keeping a monolith if it doesn't get in the way.

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 :-)


> A place I worked at recently insisted on JWTs despite being a monolith and calling the database on every API endpoint.

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.


You're right, but the parent probably meant accessing the DB for every request for stuff other than revocation.


Assuming they don't manage to consolidate everything they need for a request into 1 query, one less query is still one less query. They might be cutting their db load in ~half if they had one auth query and one query for the action.


I did. Thanks :)


In Macaroonia, which is one of the most important Biscuit inspirations, you can address "stateless" revocation with a 3P caveat on a revocation check service, which can issue time-limited "not revoked" attestations; the services that rely on the tokens don't even have to understand what that revocation check service is, or have any revocation checking code at all; it's all hidden from the application, the service can get moved around, scaled out independently, etc.

I'd be surprised if there wasn't a similar strategy available to Biscuits.


there's a siimilar concept in Biscuit, the 3rd party block: https://www.biscuitsec.org/blog/third-party-blocks-why-how-w... It's not advertised a lot right now because it's not supported by all libraries yet


Maybe a naive question: but is there any way at all (impractical or not) to implement stateless revocation? In my mind JWT is already non-stateless in the sense that the server at least needs to keep one piece of information which is the secret key. It's very memory efficient, but not stateless. If a decision is made to revoke a user, that state needs to be kept somewhere.


No. But a stateful revocation service can be extremely lightweight. The number of JWTs being revoked will be minimal and they are only revoked until they would otherwise expire, which keeps the dataset small. This could easily fit in an in memory cache to avoid a database hit.


You could make your expiration short enough that you are unlikely to need revocation, and allow token exchange (trade an expired token for a new one, and that would include a revocation check)


I believe from when I looked at it you have to store the revocation tokens in a list so it isn't an amazing solution but better than storing the whole token ig


It's generally better because revocation lists are assumed to be smaller than valid lists.


This is a neat project.

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.


(biscuit author here) you caught me, I can't help getting excited about the project XD

> 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


oh and for OAuth, it can integrate well with it: https://www.biscuitsec.org/blog/oauth-oidc/


> Given a long-term token, can I create short-term, auto-expiring tokens?

I suspect the answer is: yes, via attenuation. :)


Really cool concept but having a really hard time understanding the datalog language and the semantics.

Like specifically what is the difference between check if and allow if?


check if: if any one of those fails, the entire authorization fails allow if/deny if: they are tried in order, we stop at the first that matches. If an allow if matches and all checks passed, then the request is authorized


Im not trying to be annoying but I still don’t get it. It might be easier for me with some examples. I’m sure when folks start using this, it’ll be more clear.


Something like this needs traction. The concept is essentially a "policy DSL," where we need a domain specific language for describing authorization across the internet. The other feature a DSL should have is a 5W's approach, of who, what, when, where, why, and how - as this will make it consistent with most business logic and also any logging and audit requirements, as this is how humans describe things. I can see why they are using a concrete language based on security concepts to describe the authorizations, but the next level would be a more general subject-predicate graph form. The risk there is losing consistency when users and developers don't abstract it well, so maybe biscuitsec has got it right. All authN systems die of overspecification, and so the best solution will be the one people actually use.

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.



Somewhat? Browser cookies can literally contain anything (not just session identifiers, or authentication tokens), I've only heard JWT pronounced "Jay-double you-tea" (but maybe that's regional), Macaroons[0] and Maracrons[1] are French (given clever-cloud is french, this is strange), OPA doesn't need to be server side.

[0]: https://en.wikipedia.org/wiki/Macaroon [1]: https://en.wikipedia.org/wiki/Macaron


I think the RFC pronunciation guide

> the same as the English word "jot".

Only makes sense if your dialect has the cot-caught merger[0]

[0] https://en.wikipedia.org/wiki/Cot%E2%80%93caught_merger


You skipped an important part of the quote from RFC-7519[0] (later replaced by two RFCs[1],[2] without the strange suggestion):

> 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


Those RFCs don’t replace 7519, they are extensions and best practices. “Jot” is still indeed the intended pronunciation, whether you like it or not.


I don't, so perhaps I'm just doomed not to understand, but I still don't get that? I would never pronounce it 'jaught' either (nor know what someone meant if they did).


Only barely makes sense, you mean. And only if you squint...


> I've only heard JWT pronounced "Jay-double you-tea"

That doesn't save any effort over "JSON web token".


20% less syllables


Those options are both 5 syllables.


The offline attenuation concept is really cool. I wonder how many chained attenuations you could perform before you start to run into practical limitations with storing them attenuated token in cookies, etc.


It sounds neat - but in-practice it's something other schemes have had for yonks, albeit centralized instead of being entirely at the discretion of the subject (e.g. OAuth2's delegation grant), and there's the obvious risk of someone's token being used to counter-sign or delegate permission without their knowledge (this isn't unrealistic considering the sheer number of daily posters to StackOverflow who think browser's localStorage is an acceptable place to hold secrets and keys...).


What do you mean? How do you do an offline attenuation of an OAuth2 token?


You can’t do it offline; but in practice that isn’t a problem because IdPs generally are five-nines available.


No, it is a problem, because the motivating use case for offline attenuation is doing JIT minimization of tokens before sending them. IdP OAuth2 tokens tend to be all-powerful, a game-over break if stolen. That's why offline attenuation was invented.

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".


You can of course use Macaroons with OAuth, which was something that I tried to get the OAuth WG interested in, with little success. But I did get it added to my then employer’s AS product: https://neilmadden.blog/2020/07/29/least-privilege-with-less...

(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.


It's a really good, clarifying post; I've had it in a tab for a week or so while I've been writing.


Where should web pages store secrets and keys?


You have a couple of options for storing tokens or other secrets like API keys that only need to be presented to a remote server.

* 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.


Thesedays they typically use a (native) reverse-proxy as their gateway to the wider web, which manages their access-tokens/secrets for them.

c.f.: https://leastprivilege.com/2020/03/31/spas-are-dead/


I agree, the current OAuth2/OIDC path of having to inline a non-standard (at the moment) token exchange grant absolutely sucks.



(author of the paper in your second link, which the Biscuit spec also references)

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.


That abstraction is also a strength. I think one of the reasons Macaroons really resonate with security engineers is that the underlying cryptography is simple and user-proof, and the resulting formats and protocols are super flexible†, which is not a combination you typically get with cryptosystems. It's also one of the truly great security papers; having worked with a team for a year scaling them out, I get something new every time I read it. My only quibble is the internal naming ("CID", "VID").

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.


Very much looking forward to the blog post! At G we’ve relied on some existing key replication APIs and the caveats have just been the simplest possible and rigid protobufs for each application.

(And thanks! I’m still citing your Velvet Underground comparison.)


I’m very intrigued and excited about this! I read your earlier post comparing schemes and saying after years beating the drum for macaroons, after trying to implement them, you wouldn’t beat that drum any more. Have you come back around?


`It's complicated`. :)


That's great Looking forward to reading the blog. Do you plan to publish any open source libraries for it?


HMAC is used with the same purpose as in FIDO?


Not really, no, if you refer to them being used for chaining. In FIDO it’s just used as a plain MAC in the expected places.


tptacek wrote up a rather nice comparative survey of the field, including Biscuits and Macaroons

https://fly.io/blog/api-tokens-a-tedious-survey


We also did an episode of the podcast with Geoffroy:

https://securitycryptographywhatever.com/2022/01/29/biscuits...


This was a great episode, another great one I remember is https://securitycryptographywhatever.com/2021/08/12/what-do-...

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)


Tldr client-server is different than server-server, you should think about your use case and what you need from your auth system, but tptacek thought long and hard about each at two different companies and decided on macaroons in both cases. Good to know!

(Just kidding, ish, and though they're quite long they're not too long and I did read; would recommend.)


I remember seeing a talk[1] at Gophercon a while back on how nightmarish macaroons were to implement.

[1] https://www.youtube.com/watch?v=MZFv62qz8RU


First, I don't think that's a good characterization of the talk.

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.


If you solve stateless token revocation, you've broken the laws of physics.


After cookies, now we have biscuits. How about some Tiramisu?


I'm disappointed at the lack of relevance of these confectionary-related names. "Cookie" means "small cake" (in other languages, the word "cook" and "cake" are the same word). "Biscuit" means "twice cooked" (from Latin bis (twice) and French cuit (cooked)). It's already disappointing that cookie and biscuit are considered synonymous but simply American vs British when there is a meaningful distinction. Cookies are just small cakes, biscuits are small cakes that are baked again to dry them out. These technologies seem to have nothing to do with the foods, though.


Interesting to see

"Portable - Biscuit is implemented in Rust, Haskell, Go, Java, WebAssembly, C..."

but nothing about Javascript, NodeJS and Python, which seem very common these days.


According to their docs, the NodeJS and Python libraries seem to be wrappers around the Rust implementation.

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.


webassembly may be useable in many javascript environments


I ported biscuit-java to Kotlin for an internal project. In the course of doing so, I went from a naive superfan to a somewhat grizzled advocate. Here's my high level summary:

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.


(biscuit maintainer here)

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


[flagged]


Why do we feel using variable length integers in protobuf's wire format vs strings is an instant deal breaker? Isn't understanding a protocol is going to require a more documentation than just a string field name? The kind of documentation that can often be found as comments in a protocol buffer definition file?

Having written a binary serialization format that does make the choice to use strings for field identifiers on the wire (https://litevectors.org), I can still see the appeal of using integers - they're smaller and parse faster. I've used protocol buffers in several places, including in an embedded microcontroller where parsing something like JSON would have been a lift. Aside from some issues with the impact of deeply nested buffers on the stack size, protocol buffers worked brilliantly, and allowed us to keep a consistent data representation across multiple languages and platforms.


(biscuit author here) I tried a lot of different formats, especially looking for something that generated small tokens, that could have a canonical form, and that was supported in a lot of languages. The canonical part was a dead end as most formats did not support that well. Protobuf ended up being the one which generated the smallest tokens, and was easily supported in most languages, even if, like you, I don't agree with everything in the way it's serialized. Self describing formats were a dead end as well because it grows the token size


What do you use instead?




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

Search: