From 0c71ea8cd4a5b3e7fc0196954683207470c7d59b Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Wed, 22 Feb 2023 00:17:02 +0100 Subject: [PATCH] guide reproducing JWT scope with attenuation --- content/docs/guides/scopes.md | 59 +++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 content/docs/guides/scopes.md diff --git a/content/docs/guides/scopes.md b/content/docs/guides/scopes.md new file mode 100644 index 0000000..b3f03b8 --- /dev/null +++ b/content/docs/guides/scopes.md @@ -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 %} \ No newline at end of file