Modeling User Groups
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 guide you will learn how to add users to groups and grant groups access to an object using Auth0 FGA.
Adding a relationship tuple specifying that a group has a relation to an object is helpful in cases where you want to encompass a set of users with the same relation to an object. For example:
- Grant a group of
engineers
viewer
access toroadmap.doc
- Create a
block_list
ofmembers
who can't access adocument
- Sharing a
document
with ateam
- Granting
viewer
access to aphoto
tofollowers
only - Making a
file
viewable for allusers
within anorganization
- Restricting access from or to
users
in a certainlocale
Before you start
In order to understand this guide correctly you must be familiar with some Auth0 FGA Concepts and know how to develop the things that we will list below.
Assume that you have the following authorization model.
You have an object called document
that users can be related to as an editor
.
You have an object called
document
that users can be related to as an editor
.- DSL
- JSON
type document
relations
define editor as self
{
"type_definitions": [
{
"type": "document",
"relations": {
"editor": {
"this": {}
}
}
}
]
}
In addition, you will need to know the following:
Modeling Basics
You need to know how to create an authorization model and create a relationship tuple to grant a user access to an object. 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
As we develop our application, we might encounter use cases where a group of users have a certain role or permission on an object. For example, members
of a certain team
might have an editor
relation to a certain document
.
In order to represent this in Auth0 FGA, we need:
- Introduce the concept of a
team
to the authorization model - Add users as
members
to theteam
- Assign the
team
members a relation to an object - Checking an individual member's access to the object
01. Introduce the concept of a team to the authorization model
We need to define the object team
in our authorization model. In our use case, a team
can have member
s, so we make the following changes to our authorization model:
- DSL
- JSON
type document
relations
define editor as self
type team
relations
define member as self
{
"type_definitions": [
{
"type": "document",
"relations": {
"editor": {
"this": {}
}
}
},
{
"type": "team",
"relations": {
"member": {
"this": {}
}
}
}
]
}
02. Add users as members to the team
We can now assign users as member
s of team
s. Let's create a new relationship tuple that states alice is a member of team:writers.
- Node.js
- Go
- .NET
- curl
- Pseudocode
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,
});
await fgaClient.write({
writes: {
tuple_keys: [
{ user: 'alice', relation: 'member', object: 'team:writers'}
]
}
});
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.WriteRequest{
Writes: &fgaSdk.TupleKeys{
TupleKeys: []fgaSdk.TupleKey {
{
User: fgaSdk.PtrString("alice"),
Relation: fgaSdk.PtrString("member"),
Object: fgaSdk.PtrString("team:writers"),
},
},
},
}
_, response, err := fgaClient.Auth0FgaApi.Write(context.Background()).Body(body).Execute()
if err != nil {
// .. Handle error
}
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);
}
}
await fgaClient.Write(new WriteRequest{
Writes = new TupleKeys(new List<TupleKey>() {
new() { User = "alice", Relation = "member", Object = "team:writers" }
})
});
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/write \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"writes": { "tuple_keys" : [{"user":"alice","relation":"member","object":"team:writers"}] }}'
write([
{
"user":"alice",
"relation":"member",
"object":"team:writers"
}
])
03. Assign the team members a relation to an object
To represent groups we use the type:object_id#relation
format, which represents the set of users related to the type:object_id
as a certain relation. For example, team:writers#members
is used to represent the set of users related to the team:writers object as member
s.
In order to assign member
s of a team
a relation to a document
, we can create the following relationship tuple that states that members of team:writers are editors of document:meeting_notes.doc.
- Node.js
- Go
- .NET
- curl
- Pseudocode
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,
});
await fgaClient.write({
writes: {
tuple_keys: [
// Set of users related to 'team:writers' as 'member'
{ user: 'team:writers#member', relation: 'editor', object: 'document:meeting_notes.doc'}
]
}
});
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.WriteRequest{
Writes: &fgaSdk.TupleKeys{
TupleKeys: []fgaSdk.TupleKey {
{
// Set of users related to 'team:writers' as 'member'
User: fgaSdk.PtrString("team:writers#member"),
Relation: fgaSdk.PtrString("editor"),
Object: fgaSdk.PtrString("document:meeting_notes.doc"),
},
},
},
}
_, response, err := fgaClient.Auth0FgaApi.Write(context.Background()).Body(body).Execute()
if err != nil {
// .. Handle error
}
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);
}
}
await fgaClient.Write(new WriteRequest{
Writes = new TupleKeys(new List<TupleKey>() {
// Set of users related to 'team:writers' as 'member'
new() { User = "team:writers#member", Relation = "editor", Object = "document:meeting_notes.doc" }
})
});
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/write \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"writes": { "tuple_keys" : [{"user":"team:writers#member","relation":"editor","object":"document:meeting_notes.doc"}] }}'
write([
// Set of users related to 'team:writers' as 'member'
{
"user":"team:writers#member",
"relation":"editor",
"object":"document:meeting_notes.doc"
}
])
04. Checking an individual member's access to an object
Now that we have:
- a relationship tuple indicating that alice is an
member
of team:writers - a relationship tuple indicating that members of team:writers are editors of document:meeting_notes.doc
This means that if we *check*is alice an editor 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: 'alice',
relation: 'editor',
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("alice"),
Relation: fgaSdk.PtrString("editor"),
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 = "alice",
Relation = "editor",
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":"alice","relation":"editor","object":"document:meeting_notes.doc"}}'
# Response: {"allowed":true}
check(
"alice", // check if the user `alice`
"editor", // has an `editor` relation
"document:meeting_notes.doc", // with the object `document:meeting_notes.doc`
);
Reply: true
is alice related to document:meeting_notes.doc as editor?
# Response: A green path from the user to the object indicating that the response from the API is `{"allowed":true}`
The chain of resolution becomes:
- alice is
member
of team:writers member
s of team:writers areeditor
s of document:meeting_notes- therefore, alice is
editor
of document:meeting_notes
caution
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.