biscuit-net is an implementation of Biscuit in .NET/C#. It aims to be fully compatible with other existing implementations, so that tokens issued by, for example, the Rust version, could be validated by this library and vice versa.
- biscuit website for documentation and examples
- biscuit specification
- biscuit-rust for some more technical details.
Taken from biscuit-rust:
Biscuit is an authorization token for microservices architectures with the following properties:
- decentralized validation: any node could validate the token only with public information;
- offline delegation: a new, valid token can be created from another one by attenuating its rights, by its holder, without communicating with anyone;
- capabilities based: authorization in microservices should be tied to rights related to the request, instead of relying to an identity that might not make sense to the verifier;
- flexible rights managements: the token uses a logic language to specify attenuation and add bounds on ambient data;
- small enough to fit anywhere (cookies, etc).
Non goals:
- This is not a new authentication protocol. Biscuit tokens can be used as opaque tokens delivered by other systems such as OAuth.
- Revocation: while tokens come with expiration dates, revocation requires external state management.
These usage samples and additinal ones, as well es required usings can be found in BiscuitBuilderTests.cs.
var rootKey = Ed25519.NewSigningKey();
var token = Biscuit.New(rootKey)
.AuthorityBlock("""
right("/a/file1.txt", "read");
right("/a/file1.txt", "write");
right("/a/file2.txt", "read");
right("/a/file2.txt", "write");
""")
.EndBlock()
.Serialize();
// token is now a byte[], ready to be shared
var attenuatedToken = Biscuit.Attenuate(token)
.AddBlock()
.Add("""check if resource("file4")""")
.Add("""check if resource("file5")""")
.EndBlock()
.Serialize();
// attenuatedToken is now a byte[] attenuation of the original token, and ready to be shared
var verificationKey = new Ed25519.VerificationKey(rootKey.Public);
if(!Biscuit.TryDeserialize(token, verificationKey, out var biscuit, out var formatErr))
{
throw new Exception($"Couldn't deserialize/validate biscuit: {formatErr}");
}
if(!Parser.Authorizer("""resource("file5"); allow if true;""").TryAuthorize(biscuit, out err))
{
throw new Exception($"Couldn't authorize biscuit: {err}");
}
Sealing a biscuit means it can no longer be attenuated.
var rootKey = Ed25519.NewSigningKey();
var token = Biscuit.New(rootKey)
.AuthorityBlock()
.Add("resource", "file4")
.EndBlock()
.Seal();
//this will throw an exception
Biscuit.Attenuate(token);
You can learn more about third-party blocks in the relevant section of the Biscuit specifiction, or in this blog Post.
var rootKey = Ed25519.NewSigningKey();
var thirdPartyKey = Ed25519.NewSigningKey();
var verificationKey = new Ed25519.VerificationKey(rootKey.Public);
var token1 = Biscuit.New(rootKey)
.AuthorityBlock()
.Add("resource", "file4")
.EndBlock()
.Serialize();
var token2 = Biscuit.Attenuate(token1)
.AddThirdPartyBlock(request =>
//the request would usually be send to a third-party over the wire
//the third party processes the requests, builds a third-party block, signs
//it, it sends it back.
//for the sake of the example, everything here happens in-process
Biscuit.NewThirdParty()
.Add("""check if resource("file4")""")
.Add("""check if resource("file5")""")
.Sign(thirdPartyKey, request)
)
.Serialize();
// token2 is now a byte[], containing the third party block
var rootKey = Ed25519.NewSigningKey();
var thirdPartyKey = Ed25519.NewSigningKey();
var token1 = Biscuit.New(rootKey)
.AuthorityBlock()
.Add("resource", "file4")
.EndBlock()
.AddBlock()
//this block trusts any blocks signed by the thirdPartyKey
//even if these blocks have been appended only later-on
.Trusts(thirdPartyKey.Public)
.Add("""check if resource("file5");""")
.EndBlock()
.AddBlock()
.AddCheck(Check.CheckKind.One)
.AddRule("""resource("file5")""")
//while the overall block only trusts authority and the authorizer
//this rule also trusts blocks signed by the thirdPartyKey
.Trusts(thirdPartyKey.Public)
.EndRule()
.EndCheck()
.EndBlock()
.Serialize();
The most interesting bits of this implementation are:
- Datalog Interpreter
- ANTLR based parser grammar
- Ed2559 cryptography, using NSec/libsodium
The implementation currently passes all the tests of the conformance test suite published as part of the Biscuit specification.
There are also additional unit-like tests, however those could be a more complete. Feel free to chip in.
NuGet preleases are currently available on Github Packages. To consume them, your project needs to configure an additional NuGet source like follows.
# create a new project-specif nuget.config, if you don't already have one
dotnet new nugetconfig
# add the GitHub nuget source
dotnet nuget add source https://nuget.pkg.github.com/dmunch/index.json
# install biscuit_net.Parser, which pulls in the other packages as dependies and is required for the examples to work
dotnet add package biscuit_net.Parser
This code should NOT be used in production scenarios before further scrutinizing and reviewing happend. You have been warned.
Licensed under Apache License, Version 2.0.