Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

guide reproducing JWT scope with attenuation #60

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions content/docs/guides/scopes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
+++
title = "Scopes based authorization"
description = "Reproducing JWT scopes with Biscuit"
date = 2023-02-21T08:00:00+00:00
updated = 2023-02-21T08:00:00+00:00
draft = false
weight = 10
sort_by = "weight"
template = "docs/page.html"

[extra]
lead = "Reproducing JWT scopes with Biscuit"
toc = true
top = false
+++

When coming to Biscuit from JWT based systems, it can be challenging to migrate tokens and authorization patterns at the same time. While most JWT claims like `aud` or `exp` are straightforward to reproduce as Biscuit policies, `scope` requires a bit more work. But this is where we get the quick wins with delegation!

In a JWT, scopes come as a space separated string, like this:

```json
{
"scope": "read:article write:article read:comment write:comment"
}
```

In a Biscuit token, we can translate that as a fact containing a set of scopes:

{% display() %}
scope(["read:article", "write:article", "read:comment", "write:comment"]);
{% end %}

And when we get a request trying to write an article, we would check for the relevant scope like this:

{% display() %}
check if scope($scopes), $scopes.contains("write:article");
allow if true;
{% end %}

We can even check the presence of multiple scopes: `check if scope($scopes), $scopes.contains(["read:comment", "write:comment"])`.

While this is already useful for an initial migration, we can introduce attenuation with a small change. What if we could restrict the set of scopes by attenuating the token?

The `check all` syntax allows use to verify a condition on all the facts that are matched. So if we had the same token as above, then attenuated to one containing the same scopes, but without `write:article`, we would get this authorizer content:

{% datalog() %}
// scopes from the first block
scope(["read:article", "write:article", "read:comment", "write:comment"]);
// scopes from the second block
scope(["read:article", "read:comment", "write:comment"]);

// this succeeds because "read:article" is present in both blocks
check all scope($scopes), $scopes.contains("read:article");

// this fails because "write:article" is absent from the second blocks
check all scope($scopes), $scopes.contains("write:article");

allow if true;
{% end %}