Concentric Relationships
note
Please note that at this point in time, it is not considered production-ready and does not come with any SLAs; availability and uptime are not guaranteed. Limitations of Auth0 FGA during the Developer Community Preview can be found here.
In this short guide, you'll learn how to represent a concentric relationships.
For example, if you want to have all editors of a document also be viewers of said document.
Concentric relations make the most sense when your domain logic has nested relations, where one having relation implies having another relation.
For example:
- all
editors
areviewers
- all
managers
aremembers
- all
device_managers
aredevice_renamers
This allows you to only create a single relationship tuple rather than creating n relationship tuples for each relation.
Before you start
To better understand this guide, you should be familiar with some Auth0 FGA Concepts and know how to develop the things listed below.
You will start with the authorization model below, it represents a document
type that can have users related as editor
and viewer
.
Let us also assume that we have a document
called "meeting_notes.doc" and bob is assigned as editor to this document.
document
type that can have users related as editor
and viewer
.document
called "meeting_notes.doc" and bob is assigned as editor to this document.- DSL
- JSON
type document
relations
define viewer as self
define editor as self
{
"type_definitions": [
{
"type": "document",
"relations": {
"viewer": {
"this": {}
},
"editor": {
"this": {}
}
}
}
]
}
The current state of the system is represented by the following relationship tuples being in the system already:
[
{
"user": "bob",
"relation": "editor",
"object": "document:meeting_notes.doc",
},
]
In addition, you will need to know the following:
Modeling User Groups
You need to know how to add users to groups and grant groups access to resources. Learn more →
Auth0 FGA Concepts
- A Type: a class of objects that have similar characteristics
- A User: an entity in the system that can be related to an object
- A Relation: is a string defined in the type definition of an authorization model that defines the possibility of a relationship between objects of this type and other users in the system
- An Object: represents an entity in the system. Users' relationships to it can be define through relationship tuples and the authorization model
- A Relationship Tuple: a grouping consisting of a user, a relation and an object stored in Auth0 FGA
Step by Step
With the current type definition, there isn't a way to indicate that all editors
of a certain document
are also automatically viewers
of that document. So for a certain user, in order to indicate that they can both edit
and view
a certain document
, two relationship tuples need to be created (one for editor
, and another for viewer
).
01. Modify our model to imply editor as viewer
Instead of creating two relationship tuples, we can leverage concentric relationships by defining editors are viewers.
Our authorization model becomes the following:
- DSL
- JSON
type document
relations
define viewer as self or editor
define editor as self
{
"type_definitions": [
{
"type": "document",
"relations": {
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "editor"
}
}
]
}
},
"editor": {
"this": {}
}
}
}
]
}
info
viewer
of a document
are any of:
- users that are directly assigned as
viewer
- users that have
editor
of the document
With this authorization model change, having an editor
relationship with a certain document implies having a viewer
relationship with that same document.
02. Check that editors are viewers
Since we had a relationship tuple that indicates that bob is an editor
of document:meeting_notes.doc, this means bob is now implicitly a viewer
of document:meeting_notes.doc.
If we now check: is bob a viewer of document:meeting_notes.doc? we would get the following:
- Node.js
- Go
- .NET
- curl
- Pseudocode
- Playground
Initialize the SDK
// FGA_ENVIRONMENT can be "us" (default if not set) for Developer Community Preview or "playground" for the Playground API
// import the SDK
const { Auth0FgaApi } = require('@auth0/fga');
// Initialize the SDK
const fgaClient = new Auth0FgaApi({
environment: process.env.FGA_ENVIRONMENT,
storeId: process.env.FGA_STORE_ID,
clientId: process.env.FGA_CLIENT_ID,
clientSecret: process.env.FGA_CLIENT_SECRET,
});
// Run a check
const { allowed } = await fgaClient.check({
tuple_key: {
user: 'bob',
relation: 'viewer',
object: 'document:meeting_notes.doc',
},});
// allowed = true
Initialize the SDK
// FGA_ENVIRONMENT can be "us" (default if not set) for Developer Community Preview or "playground" for the Playground API
import (
fgaSdk "github.com/auth0-lab/fga-go-sdk"
"os"
)
func Main() {
configuration, err := fgaSdk.NewConfiguration(fgaSdk.UserConfiguration{
Environment: os.Getenv("FGA_ENVIRONMENT"),
StoreId: os.Getenv("FGA_STORE_ID"),
ClientId: os.Getenv("FGA_CLIENT_ID"),
ClientSecret: os.Getenv("FGA_CLIENT_SECRET"),
})
if err != nil {
// .. Handle error
}
fgaClient := fgaSdk.NewAPIClient(configuration)
}
body := fgaSdk.CheckRequest{
TupleKey: &fgaSdk.TupleKey{
User: fgaSdk.PtrString("bob"),
Relation: fgaSdk.PtrString("viewer"),
Object: fgaSdk.PtrString("document:meeting_notes.doc"),
},
data, response, err := fgaClient.Auth0FgaApi.Check(context.Background()).Body(body).Execute()
// data = { allowed: true }
Initialize the SDK
// FGA_ENVIRONMENT can be "us" (default if not set) for Developer Community Preview or "playground" for the Playground API
// import the SDK
using Auth0.Fga.Api;
using Auth0.Fga.Configuration;
using Environment = System.Environment;
namespace ExampleApp;
class MyProgram {
static async Task Main() {
var storeId = Environment.GetEnvironmentVariable("FGA_STORE_ID");
var environment = Environment.GetEnvironmentVariable("FGA_ENVIRONMENT")
var configuration = new Configuration(storeId, environment) {
ClientId = Environment.GetEnvironmentVariable("FGA_CLIENT_ID"),
ClientSecret = Environment.GetEnvironmentVariable("FGA_CLIENT_SECRET"),
};
var fgaClient = new Auth0FgaApi(configuration);
}
}
// Run a check
var response = await fgaClient.Check(new CheckRequest(new TupleKey {
User = "bob",
Relation = "viewer",
Object = "document:meeting_notes.doc"
});
// response.Allowed = true
Get the Bearer Token and set up the FGA_API_URL environment variable
# Not needed when calling the Playground API
curl -X POST \
https://fga.us.auth0.com/oauth/token \
-H 'content-type: application/json' \
-d '{"client_id":"'$FGA_CLIENT_ID'","client_secret":"'$FGA_CLIENT_SECRET'","audience":"https://api.us1.fga.dev/","grant_type":"client_credentials"}'
# The response will be returned in the form
# {
# "access_token": "eyJ...Ggg",
# "expires_in": 86400,
# "scope": "read:tuples write:tuples check:tuples ... write:authorization-models",
# "token_type": "Bearer"
# }
# Store this `access_token` value in environment variable `FGA_BEARER_TOKEN`
# For non-playground environment
FGA_API_URL='https://api.us1.fga.dev'
# For playground environment
# FGA_API_URL='https://api.playground.fga.dev'
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"tuple_key":{"user":"bob","relation":"viewer","object":"document:meeting_notes.doc"}}'
# Response: {"allowed":true}
check(
"bob", // check if the user `bob`
"viewer", // has an `viewer` relation
"document:meeting_notes.doc", // with the object `document:meeting_notes.doc`
);
Reply: true
is bob related to document:meeting_notes.doc as viewer?
# Response: A green path from the user to the object indicating that the response from the API is `{"allowed":true}`
Note
When creating relationship tuples for Auth0 FGA make sure to use unique ids for each object and user within your application domain. We're using first names and simple ids to just illustrate an easy-to-follow example.