Biscuit Authorization Part I
Space and Time relies on biscuits to support decentralized authorization. On the Space and Time platform, Biscuits are used to authorize access to resources (e.g. tables). To learn more about how Biscuits are used in Space and Time, visit our developer docs or check out this video.
It’s often painful to write and check authorization codes on each and every platform. Biscuit tokens help us save time and effort when making secure microservice applications, so we don’t have to worry about managing authorization across different services. Biscuits let us make tokens offline and authorize them by sending them to the server in the form of cookies. Here is a diagram to illustrate how Biscuit can be utilized across platforms.
Long tutorial alert!
This tutorial is a part of 2 tutorial series.
- Part 1: This tutorial covers the basics of Biscuits, creating a Biscuit with a Command Line interface (CLI), and checking the authority of the token. We will also learn to attenuate the Biscuit tokens.
- Part 2: Upcoming tutorial. This covers how to use Biscuits with Golang.
What are Biscuit authorization tokens?
Biscuit Authorization tokens are bearer tokens that hold information about a user’s permissions (authorization) for a given application. That means you don't have to create a separate authorization table for users. The essence of Biscuit tokens can be better understood in a microservices architecture with multiple applications across multiple servers. Biscuit, being a token, can be used in any microservice application with a public key without any language dependency. Furthermore, permissions in Biscuits can be extended for a new type of user with fewer rights than the current user in the hierarchy. This process is called attenuation. Attenuation is achieved by writing the authorization policies in Datalog, a declarative programming language. The following diagram illustrates a use case scenario for a forum website using Biscuits.
Finally, if you don’t want the Biscuit token to be further attenuated, you can seal the Biscuit. Also, you can manually decommission a Biscuit token using the revocation id.
You can port Biscuit tokens to various applications and implement them in multiple programming languages. The only things needed for its implementation are Protobuf generator and Ed25519 signing. We’ll look at how the CLI is used to implement Biscuit authorization, and in Part 2, we’ll learn how to use Biscuits with Golang.
Biscuit vs. JWT vs. Macaroons
Biscuit combines the goodness of both JWT and Macaroons to deliver the best authorization options for an application while providing the best security.
Components in Biscuit authorization
A few important terms to remember:
Facts are data or truth. Existing facts cannot be changed but a new fact can be generated from existing ones using Rules (explained below).
For example, facts can be:
Rules can be used to generate new facts. They accept facts as parameters and combine them with other facts to generate new ones.
Example of rules:
This rule generates a new fact like:
Checks are used to validate facts against certain values.
The above condition checks if the roles list contains “moderator“. These checks can be written both in Biscuit token blocks or can also be written in the server-side application while validating Biscuits.
A Biscuit token is made up of blocks. Each block can contain facts, rules, and checks. There is one block by default which is called Authority Block. Other blocks are user-generated.
Allow/Deny conditions come after Checks, if any, and they allow or deny access to resources. They can be used only in authorizer applications and not with Biscuit tokens.
The above code can be interpreted as, if both the conditions on the right side pass, then only the code will allow access to the resource.
Namespaces are used to avoid accidental collision while naming facts in a microservices architecture.
Here both “Users” are from different namespaces and do not collide when writing rules.
Trying the Command Line Interface (CLI)
The Biscuit Command Line Interface (CLI) is the fastest way to try out the Biscuit tokens. We will learn how to install the CLI and run commands to create, authorize, and attenuate Biscuit. For detailed commands, visit this page.
Installing the CLI
You can either download the source code or use the Cargo package manager to install the binary file. We will follow the latter and won’t focus on compiling from source. Cargo is the package manager for the Rust programming language, and it should already be on your computer for this tutorial. Then, you can install Biscuit CLI using the following command:
Note: You might need to set the environment variable path for biscuit-cli located in <install path>/cargo/bin. On Ubuntu, you can set this by adding the path to the /etc/environment file and then running the following command to instantly make the terminal parse the changes in the file:
Generating public/private keypair
Once you install the CLI, you can create a basic public/private keypair for your application. The server stores the public key, and it can use it to verify any token signed by the private key. Example:
Note: Create new files named private-key and public-key. Save the above-obtained private and public keys to the files, respectively. Don’t lose the private key, or else you won’t be able to authenticate your keys in the future. Regenerating the private key will be the only option then.
Creating Biscuit token
Authorization files can contain hardcoded Datalog blocks or scripts. For the sake of this tutorial, we’ll stick to hardcoded Datalog blocks. If you want to learn Datalog scripting, please visit this link.
We will save the following content to the file as “authority.datalog.”
The block above describes the role of the user holding this Biscuit token. These data are called “facts” and will be checked later against the rules on the server. The facts can be hardcoded data or can be derived using various rules.
The following type of content can be added to a Biscuit token:
- Facts: Hardcoded data like user(["admin", "moderator"])
- Rules: To generate more facts based on conditions. Explained later.
- Checks: Add checks to limit the token. They are also used in attenuation which is described later.
Now, we will create a new Biscuit token with the above role and sign it using our private key. To create a Biscuit authorization token, use the following command
Note: We used the private-key file and authority.datalog from above.
To check the contents of the Biscuit token, use this command.
The datalog section confirms our authorization block. It also generates a revocation ID that you can use to invalidate the token if required
Check Biscuit token authority
To check the authority (allowed permissions) of an incoming Biscuit token (created above), we need to create a set of rules to check against given facts. If the Biscuit token that comes in follows the rules, the system allows the user to use the resource. In short, we need to check if a token is allowed to do the requested operation or not.
For that, we need to create another Datalog file. Let’s call it authorizer.datalog.
There are 3 facts blocks in the above file:
- Fact 1: Facts obtained from Biscuit token. Let’s say the Biscuit token was sent to the authorizer server application using a Post request. Then, the application can get this information, File contents (User(“admin”)), time of the request, and type of request (Post, in our case).
- Facts 2: Hardcoded facts from the server application. This can come in handy when you need to apply checks and conditions based on a particular server.
- Facts 3: You can pull dynamic facts from external sources like databases. In our example above, ACLs are listed.
After the Facts, we have an allow/deny condition, which checks if all the rules are followed, and the user is authorized.
Let’s see how we can authorize a user with the CLI...
After the rules and facts in the authorizer.datalog file were checked against the rules, the system passed the authorizing rules, and gave the user permission. Now we will learn how to attenuate the Biscuit tokens.
Biscuit token attenuation
Attenuation is the process of granting a user certain permissions lower than those of a current user in the hierarchy. The permissions will always be less than those of the current user. Attenuation is achieved by appending a new permission (policy) block to the existing Biscuit. This process generates a new Biscuit with less permission than the earlier one. Let’s see how to do it.
We will make a new attenuated Biscuit token with a new rule to check if the user is a moderator. Create a new file, pass-attenuated.datalog, and paste the following content:
Remember, in order to make attenuated Biscuits, we need to add new conditions to the existing Biscuit file.
Now let’s create the attenuated Biscuit authorization token using the above file:
So now you have a new attenuated Biscuit token. Let’s verify the new token and see its content using the command we ran earlier:
If you observe the output thoroughly, you will notice a new block in the Token confirming the attenuation token:
Checking the attenuated Biscuit token
Let’s finally check if the new attenuated Biscuit token is verified on the server. We can run the command that we already know:
As you can see, the server successfully verified the attenuated token. Run a few tests with different parameters on the pass-attenuated.datalog file and check what all parameters are allowed by the authorizer.
Try creating a new attenuated Biscuit with the following details and see if it passes.
Your test should result in a failure or else something was wrong in your rules or facts.
Biscuit authorization tokens are amazing authorization tokens for microservices architecture. It helps you save a lot of time when implementing authorization while also providing top-class security aspects. In this tutorial, we learned the basics of Biscuits and saw how to use the Biscuit CLI.