Modeling Authorization for an IoT Security System with Auth0 FGA
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.
This tutorial explains how to model permissions for an IoT system using Auth0 FGA.
- How to model a permission system using Auth0 FGA
- How to see Auth0 FGA Authorization in action by modeling an IoT Security Camera System
Before You Start
In order to understand this guide correctly you must be familiar with some Auth0 Fine Grained Authorization (FGA) concepts and know how to develop the things that we will list below.
Auth0 FGA Concepts
It would be helpful to have an understanding of some concepts of Auth0 FGA before you start.
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 →
Modeling Concentric Relationships
You need to know how to update the authorization model to allow having nested relations such as all writers are readers. Learn more → Used here to indicate that both IT Admins and Security Guards can view live video.
Direct Relationships
You need to know how to disallow granting direct relation to an object and requiring the user to have a relation with another object that would imply a relation with the first one. Learn more → Used here to indicate that "Rename Device" is a permission that cannot be assigned directly, but can only be granted through the "IT Admin" role.
User Groups
You need to know how to add users to groups and create relationships between groups of users and an object. Learn more →
Used here to indicate that security guards on a certain group are security guards on a device in that group.
Concepts & Configuration Language
What You Will You Be Modeling
In this tutorial, you will build an authorization model for a sample IoT Security Camera System (detailed below) using Auth0 Fine Grained Authorization (FGA). You will use some scenarios to validate the model.
The goal by the end of this post is to ask Auth0 FGA: Does person X have permission to perform action Y on device Z? In response, you want to either get a confirmation that person X can indeed do that, or a rejection that they cannot.
Requirements
These are the requirements:
- Security guards have access to view live and recorded video from Devices.
- IT Admins can view live and recorded videos, as well as rename Devices.
- To make access management easier, Devices can be grouped into Device Groups. Security guards with access to the Device Group are Security Guards with access to each Device in the group. Similarly for IT Admins.
Defined Scenarios
Use the following scenarios to be able to validate whether the model of the requirements is correct.
There will be the following users:
- Anne
- Beth
- Charles
- Dianne
These users have the following roles and permissions:
- Anne is a Security Guard with access to only Device 1
- Beth is an IT Admin with access to only Device 1
- Charles is a Security Guard with access to Device 1 and everything in Device Group 1 (which is Device 2 and Device 3)
- Dianne is an IT Admin with access to Device 1 and everything in Device Group 1
caution
In production, it is highly recommended to use unique, immutable identifiers. Names are used in this article to make it easier to read and follow.
Modeling Device Authorization
The Auth0 Fine Grained Authorization (FGA) service is based on Zanzibar, a Relationship Based Access Control system. This means it relies on object and user relations to perform authorization checks.
Starting with devices, you will learn how to express the requirements in terms of relations you can feed into Auth0 Fine Grained Authorization (FGA).
01. Writing the Initial Model for a Device
The requirements stated:
- Security guards have access to view live and recorded video from Devices.
- IT Admins can view live and recorded videos, as well as rename Devices.
The goal is to ask Auth0 FGA whether person X has permission to perform action Y on device Z. To start, you will set aside the Security Guard and IT Admin designations and focus on the actions a user can take.
The actions users can take on a device are: view live videos, view recorded videos, and rename devices. Mapping them to relations, they become: live_video_viewer, recorded_video_viewer, device_renamer.
In Auth0 FGA, the authorization model for the device would be:
- DSL
- JSON
type device
relations
define live_video_viewer as self
define recorded_video_viewer as self
define device_renamer as self
{
"type_definitions": [
{
"type": "device",
"relations": {
"live_video_viewer": {
"this": {}
},
"recorded_video_viewer": {
"this": {}
},
"device_renamer": {
"this": {}
}
}
}
]
}
02. Inserting some relationship tuples
The requirements are:
- Anne is a Security Guard with access to only Device 1
- Beth is an IT Admin with access to only Device 1
- Security Guards can view live and recorded video
- IT Admins can view live and recorded video and rename devices
Before we tackle the problem of users access to device based on their role, we will try to grant user access based on their view relationship directly.
We will first focus on Anne and Beth's relationship with Device 1.
To add Anne as live_video_viewer of device:1:
- 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: 'anne', relation: 'live_video_viewer', object: 'device:1'}
]
}
});
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("anne"),
Relation: fgaSdk.PtrString("live_video_viewer"),
Object: fgaSdk.PtrString("device:1"),
},
},
},
}
_, 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 = "anne", Relation = "live_video_viewer", Object = "device:1" }
})
});
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":"anne","relation":"live_video_viewer","object":"device:1"}] }}'
write([
{
"user":"anne",
"relation":"live_video_viewer",
"object":"device:1"
}
])
To add Anne as recorded_video_viewer of device:1
- 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: 'anne', relation: 'recorded_video_viewer', object: 'device:1'}
]
}
});
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("anne"),
Relation: fgaSdk.PtrString("recorded_video_viewer"),
Object: fgaSdk.PtrString("device:1"),
},
},
},
}
_, 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 = "anne", Relation = "recorded_video_viewer", Object = "device:1" }
})
});
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":"anne","relation":"recorded_video_viewer","object":"device:1"}] }}'
write([
{
"user":"anne",
"relation":"recorded_video_viewer",
"object":"device:1"
}
])
Likewise, we will add Beth's relationship with device:1.
- 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: 'beth', relation: 'live_video_viewer', object: 'device:1'},
{ user: 'beth', relation: 'recorded_video_viewer', object: 'device:1'},
{ user: 'beth', relation: 'device_renamer', object: 'device:1'}
]
}
});
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("beth"),
Relation: fgaSdk.PtrString("live_video_viewer"),
Object: fgaSdk.PtrString("device:1"),
},
{
User: fgaSdk.PtrString("beth"),
Relation: fgaSdk.PtrString("recorded_video_viewer"),
Object: fgaSdk.PtrString("device:1"),
},
{
User: fgaSdk.PtrString("beth"),
Relation: fgaSdk.PtrString("device_renamer"),
Object: fgaSdk.PtrString("device:1"),
},
},
},
}
_, 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 = "beth", Relation = "live_video_viewer", Object = "device:1" },
new() { User = "beth", Relation = "recorded_video_viewer", Object = "device:1" },
new() { User = "beth", Relation = "device_renamer", Object = "device:1" }
})
});
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":"beth","relation":"live_video_viewer","object":"device:1"},{"user":"beth","relation":"recorded_video_viewer","object":"device:1"},{"user":"beth","relation":"device_renamer","object":"device:1"}] }}'
write([
{
"user":"beth",
"relation":"live_video_viewer",
"object":"device:1"
},
{
"user":"beth",
"relation":"recorded_video_viewer",
"object":"device:1"
},
{
"user":"beth",
"relation":"device_renamer",
"object":"device:1"
}
])
Verification
Now that you have some relationship tuples added, you can start using it to ask some questions, e.g., whether a person has access to rename a device.
First, you will find out if anne
has permission to view the live video
on device:1
, then you will see if anne
can rename
device:1
.
Anne has live_video_viewer
relationship with device:1.
- 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: 'anne',
relation: 'live_video_viewer',
object: 'device:1',
},});
// 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("anne"),
Relation: fgaSdk.PtrString("live_video_viewer"),
Object: fgaSdk.PtrString("device:1"),
},
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 = "anne",
Relation = "live_video_viewer",
Object = "device:1"
});
// 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":"anne","relation":"live_video_viewer","object":"device:1"}}'
# Response: {"allowed":true}
check(
"anne", // check if the user `anne`
"live_video_viewer", // has an `live_video_viewer` relation
"device:1", // with the object `device:1`
);
Reply: true
is anne related to device:1 as live_video_viewer?
# Response: A green path from the user to the object indicating that the response from the API is `{"allowed":true}`
On the other hand, Anne does not have device_renamer
relationship with device:1.
- 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: 'anne',
relation: 'device_renamer',
object: 'device:1',
},});
// allowed = false
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("anne"),
Relation: fgaSdk.PtrString("device_renamer"),
Object: fgaSdk.PtrString("device:1"),
},
data, response, err := fgaClient.Auth0FgaApi.Check(context.Background()).Body(body).Execute()
// data = { allowed: false }
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 = "anne",
Relation = "device_renamer",
Object = "device:1"
});
// response.Allowed = false
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":"anne","relation":"device_renamer","object":"device:1"}}'
# Response: {"allowed":false}
check(
"anne", // check if the user `anne`
"device_renamer", // has an `device_renamer` relation
"device:1", // with the object `device:1`
);
Reply: false
is anne related to device:1 as device_renamer?
# Response: A red object indicating that the response from the API is `{"allowed":false}`
Now, check the other relationships fore Anne and Beth.
User | Object | Relation | Query | Relation? |
---|---|---|---|---|
anne | device:1 | live_video_viewer | is anne related to device:1 as live_video_viewer? | Yes |
beth | device:1 | live_video_viewer | is beth related to device:1 as live_video_viewer? | Yes |
anne | device:1 | recorded_video_viewer | is anne related to device:1 as recorded_video_viewer? | Yes |
beth | device:1 | recorded_video_viewer | is beth related to device:1 as recorded_video_viewer? | Yes |
anne | device:1 | device_renamer | is anne related to device:1 as device_renamer? | No |
beth | device:1 | device_renamer | is beth related to device:1 as device_renamer? | Yes |
03. Updating our Authorization Model to facilitate future changes
Notice how you had to add the Anne and Beth as direct relations to all the actions they can take on Device 1 instead of just stating that they are related as Security Guard or IT Admin, and having the other permissions implied? In practice this might have some disadvantages: if your authorization model changes, (e.g so that Security Guards can no longer view previously recorded videos), you would need to change relationship tuples in the system instead of just changing the configuration.
We can address this by using concentric relation models. It allows you to express that sets of users who have a relation X to the object also have relation Y. For example, anyone that is related to the device as a security_guard
is also related as a live_video_viewer
and recorded_video_viewer
, and anyone who is related to the device as an it_admin
is also related as a live_video_viewer
, a recorded_video_viewer
, and a device_renamer
.
At the end you want to make sure that checking if Anne, Beth, Charles, or Dianne have permission to view the live video or rename the device, will get you the correct answers back.
The resulting authorization model is:
- DSL
- JSON
type device
relations
define it_admin as self
define security_guard as self
define live_video_viewer as self or it_admin or security_guard
define recorded_video_viewer as self or it_admin or security_guard
define device_renamer as self or it_admin
{
"type_definitions": [
{
"type": "device",
"relations": {
"it_admin": {
"this": {}
},
"security_guard": {
"this": {}
},
"live_video_viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "it_admin"
}
},
{
"computedUserset": {
"relation": "security_guard"
}
}
]
}
},
"recorded_video_viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "it_admin"
}
},
{
"computedUserset": {
"relation": "security_guard"
}
}
]
}
},
"device_renamer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "it_admin"
}
}
]
}
}
}
}
]
}
The requirements are:
- Anne and Charles are Security Guards with access Device 1
- Beth and Dianne are IT Admins with access Device 1
- Security Guards can view live and recorded video
- IT Admins can view live and recorded video and rename devices
Instead of adding different relationship tuples with direct relations to the actions they can take, as you did in the previous section, you will only add the relation to their role: it_admin
or security_guard
.
- 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: 'anne', relation: 'security_guard', object: 'device:1'},
{ user: 'beth', relation: 'it_admin', object: 'device:1'},
{ user: 'charles', relation: 'security_guard', object: 'device:1'},
{ user: 'dianne', relation: 'it_admin', object: 'device:1'}
]
}
});
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("anne"),
Relation: fgaSdk.PtrString("security_guard"),
Object: fgaSdk.PtrString("device:1"),
},
{
User: fgaSdk.PtrString("beth"),
Relation: fgaSdk.PtrString("it_admin"),
Object: fgaSdk.PtrString("device:1"),
},
{
User: fgaSdk.PtrString("charles"),
Relation: fgaSdk.PtrString("security_guard"),
Object: fgaSdk.PtrString("device:1"),
},
{
User: fgaSdk.PtrString("dianne"),
Relation: fgaSdk.PtrString("it_admin"),
Object: fgaSdk.PtrString("device:1"),
},
},
},
}
_, 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 = "anne", Relation = "security_guard", Object = "device:1" },
new() { User = "beth", Relation = "it_admin", Object = "device:1" },
new() { User = "charles", Relation = "security_guard", Object = "device:1" },
new() { User = "dianne", Relation = "it_admin", Object = "device:1" }
})
});
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":"anne","relation":"security_guard","object":"device:1"},{"user":"beth","relation":"it_admin","object":"device:1"},{"user":"charles","relation":"security_guard","object":"device:1"},{"user":"dianne","relation":"it_admin","object":"device:1"}] }}'
write([
{
"user":"anne",
"relation":"security_guard",
"object":"device:1"
},
{
"user":"beth",
"relation":"it_admin",
"object":"device:1"
},
{
"user":"charles",
"relation":"security_guard",
"object":"device:1"
},
{
"user":"dianne",
"relation":"it_admin",
"object":"device:1"
}
])
Verification
We can now verify whether charles is related to device:1 as live_video_viewer.
- 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: 'charles',
relation: 'live_video_viewer',
object: 'device:1',
},});
// 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("charles"),
Relation: fgaSdk.PtrString("live_video_viewer"),
Object: fgaSdk.PtrString("device:1"),
},
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 = "charles",
Relation = "live_video_viewer",
Object = "device:1"
});
// 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":"charles","relation":"live_video_viewer","object":"device:1"}}'
# Response: {"allowed":true}
check(
"charles", // check if the user `charles`
"live_video_viewer", // has an `live_video_viewer` relation
"device:1", // with the object `device:1`
);
Reply: true
is charles related to device:1 as live_video_viewer?
# Response: A green path from the user to the object indicating that the response from the API is `{"allowed":true}`
Check the other relationships for anne, beth, charles and dianne.
User | Object | Relation | Query | Relation? |
---|---|---|---|---|
anne | device:1 | live_video_viewer | is anne related to device:1 as live_video_viewer? | Yes |
beth | device:1 | live_video_viewer | is beth related to device:1 as live_video_viewer? | Yes |
anne | device:1 | recorded_video_viewer | is anne related to device:1 as recorded_video_viewer? | Yes |
beth | device:1 | recorded_video_viewer | is beth related to device:1 as recorded_video_viewer? | Yes |
anne | device:1 | device_renamer | is anne related to device:1 as device_renamer? | No |
beth | device:1 | device_renamer | is beth related to device:1 as device_renamer? | Yes |
charles | device:1 | live_video_viewer | is charles related to device:1 as live_video_viewer? | Yes |
dianne | device:1 | live_video_viewer | is dianne related to device:1 as live_video_viewer? | Yes |
charles | device:1 | recorded_video_viewer | is charles related to device:1 as recorded_video_viewer? | Yes |
dianne | device:1 | recorded_video_viewer | is dianne related to device:1 as recorded_video_viewer? | Yes |
charles | device:1 | device_renamer | is charles related to device:1 as device_renamer? | No |
dianne | device:1 | device_renamer | is dianne related to device:1 as device_renamer? | Yes |
04. Modeling Device Groups
Now that you are done with devices. Let us tackle device groups.
The requirements regarding device groups were:
- Devices can be grouped into Device Groups
- Security guards with access to the Device Group are Security Guards with access to the Devices within the Device Group. Similarly for IT Admins
The type definition for the device group:
- DSL
- JSON
type device_group
relations
define it_admin as self
define security_guard as self
{
"type": "device_group",
"relations": {
"it_admin": {
"this": {}
},
"security_guard": {
"this": {}
}
}
}
With this change, the full authorization model becomes:
- DSL
- JSON
type device
relations
define it_admin as self
define security_guard as self
define live_video_viewer as self or it_admin or security_guard
define recorded_video_viewer as self or it_admin or security_guard
define device_renamer as self or it_admin
type device_group
relations
define it_admin as self
define security_guard as self
{
"type_definitions": [
{
"type": "device",
"relations": {
"it_admin": {
"this": {}
},
"security_guard": {
"this": {}
},
"live_video_viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "it_admin"
}
},
{
"computedUserset": {
"relation": "security_guard"
}
}
]
}
},
"recorded_video_viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "it_admin"
}
},
{
"computedUserset": {
"relation": "security_guard"
}
}
]
}
},
"device_renamer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "it_admin"
}
}
]
}
}
}
},
{
"type": "device_group",
"relations": {
"it_admin": {
"this": {}
},
"security_guard": {
"this": {}
}
}
}
]
}
Updating relationship tuples on roles
Remember that Charles is a Security Guard, and Dianne an IT Admin on Group 1, enter the relationship tuples below to reflect that.
- 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: 'charles', relation: 'security_guard', object: 'device_group:group1'},
{ user: 'dianne', relation: 'it_admin', object: 'device_group:group1'}
]
}
});
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("charles"),
Relation: fgaSdk.PtrString("security_guard"),
Object: fgaSdk.PtrString("device_group:group1"),
},
{
User: fgaSdk.PtrString("dianne"),
Relation: fgaSdk.PtrString("it_admin"),
Object: fgaSdk.PtrString("device_group:group1"),
},
},
},
}
_, 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 = "charles", Relation = "security_guard", Object = "device_group:group1" },
new() { User = "dianne", Relation = "it_admin", Object = "device_group:group1" }
})
});
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":"charles","relation":"security_guard","object":"device_group:group1"},{"user":"dianne","relation":"it_admin","object":"device_group:group1"}] }}'
write([
{
"user":"charles",
"relation":"security_guard",
"object":"device_group:group1"
},
{
"user":"dianne",
"relation":"it_admin",
"object":"device_group:group1"
}
])
You still need to give all the security guards of group1 a security_guard
relation to devices 2 and 3, and similarly for IT Admins. Add the following relationship tuples to do that.
- 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: 'device_group:group1#security_guard', relation: 'security_guard', object: 'device:2'},
{ user: 'device_group:group1#security_guard', relation: 'security_guard', object: 'device:3'},
{ user: 'device_group:group1#it_admin', relation: 'it_admin', object: 'device:2'},
{ user: 'device_group:group1#it_admin', relation: 'it_admin', object: 'device:3'}
]
}
});
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("device_group:group1#security_guard"),
Relation: fgaSdk.PtrString("security_guard"),
Object: fgaSdk.PtrString("device:2"),
},
{
User: fgaSdk.PtrString("device_group:group1#security_guard"),
Relation: fgaSdk.PtrString("security_guard"),
Object: fgaSdk.PtrString("device:3"),
},
{
User: fgaSdk.PtrString("device_group:group1#it_admin"),
Relation: fgaSdk.PtrString("it_admin"),
Object: fgaSdk.PtrString("device:2"),
},
{
User: fgaSdk.PtrString("device_group:group1#it_admin"),
Relation: fgaSdk.PtrString("it_admin"),
Object: fgaSdk.PtrString("device:3"),
},
},
},
}
_, 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 = "device_group:group1#security_guard", Relation = "security_guard", Object = "device:2" },
new() { User = "device_group:group1#security_guard", Relation = "security_guard", Object = "device:3" },
new() { User = "device_group:group1#it_admin", Relation = "it_admin", Object = "device:2" },
new() { User = "device_group:group1#it_admin", Relation = "it_admin", Object = "device:3" }
})
});
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":"device_group:group1#security_guard","relation":"security_guard","object":"device:2"},{"user":"device_group:group1#security_guard","relation":"security_guard","object":"device:3"},{"user":"device_group:group1#it_admin","relation":"it_admin","object":"device:2"},{"user":"device_group:group1#it_admin","relation":"it_admin","object":"device:3"}] }}'
write([
{
"user":"device_group:group1#security_guard",
"relation":"security_guard",
"object":"device:2"
},
{
"user":"device_group:group1#security_guard",
"relation":"security_guard",
"object":"device:3"
},
{
"user":"device_group:group1#it_admin",
"relation":"it_admin",
"object":"device:2"
},
{
"user":"device_group:group1#it_admin",
"relation":"it_admin",
"object":"device:3"
}
])
Verification
Now that you have finalized the model and added the relationship tuples, you can start asking some queries. Try asking the same queries you did earlier but on device 2 instead of device 1.
We can ask is dianne related to device:2 as live_video_viewer?
- 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: 'dianne',
relation: 'live_video_viewer',
object: 'device:2',
},});
// 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("dianne"),
Relation: fgaSdk.PtrString("live_video_viewer"),
Object: fgaSdk.PtrString("device:2"),
},
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 = "dianne",
Relation = "live_video_viewer",
Object = "device:2"
});
// 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":"dianne","relation":"live_video_viewer","object":"device:2"}}'
# Response: {"allowed":true}
check(
"dianne", // check if the user `dianne`
"live_video_viewer", // has an `live_video_viewer` relation
"device:2", // with the object `device:2`
);
Reply: true
is dianne related to device:2 as live_video_viewer?
# Response: A green path from the user to the object indicating that the response from the API is `{"allowed":true}`
Type any of the following queries in the TUPLE QUERIES section and press ENTER on your keyboard to see the results.
User | Object | Relation | Query | Relation? |
---|---|---|---|---|
anne | device:2 | live_video_viewer | is anne related to device:2 as live_video_viewer? | No |
beth | device:2 | live_video_viewer | is beth related to device:2 as live_video_viewer? | No |
anne | device:2 | recorded_video_viewer | is anne related to device:2 as recorded_video_viewer? | No |
beth | device:2 | recorded_video_viewer | is beth related to device:2 as recorded_video_viewer? | No |
anne | device:2 | device_renamer | is anne related to device:2 as device_renamer? | No |
beth | device:2 | device_renamer | is beth related to device:2 as device_renamer? | No |
charles | device:2 | live_video_viewer | is charles related to device:2 as live_video_viewer? | Yes |
dianne | device:2 | live_video_viewer | is dianne related to device:2 as live_video_viewer? | Yes |
charles | device:2 | recorded_video_viewer | is charles related to device:2 as recorded_video_viewer? | Yes |
dianne | device:2 | recorded_video_viewer | is dianne related to device:2 as recorded_video_viewer? | Yes |
charles | device:2 | device_renamer | is charles related to device:2 as device_renamer? | No |
dianne | device:2 | device_renamer | is dianne related to device:2 as device_renamer? | Yes |
05. Disallow Direct Relationships to users
Notice that despite following Step 03, anne and beth still have direct relations to all the actions they can take on device:1.
Updating the authorization model
anne
is a live_video_viewer
by both her position as security_guard
as well as her direct relationship assignment. This is undesirable. Imagine anne
left her position of security_guard
and she will still have live_video_viewer
access to device:1
.
To remedy this, remove self
from live_video_viewer
, recorded_video_viewer
and device_renamer
. This denies direct relations to live_video_viewer
, recorded_video_viewer
and device_renamer
from having an effect. To do this:
- DSL
- JSON
type device
relations
define it_admin as self
define security_guard as self
define live_video_viewer as it_admin or security_guard
define recorded_video_viewer as it_admin or security_guard
define device_renamer as it_admin
type device_group
relations
define it_admin as self
define security_guard as self
{
"type_definitions": [
{
"type": "device",
"relations": {
"it_admin": {
"this": {}
},
"security_guard": {
"this": {}
},
"live_video_viewer": {
"union": {
"child": [
{
"computedUserset": {
"relation": "it_admin"
}
},
{
"computedUserset": {
"relation": "security_guard"
}
}
]
}
},
"recorded_video_viewer": {
"union": {
"child": [
{
"computedUserset": {
"relation": "it_admin"
}
},
{
"computedUserset": {
"relation": "security_guard"
}
}
]
}
},
"device_renamer": {
"union": {
"child": [
{
"computedUserset": {
"relation": "it_admin"
}
}
]
}
}
}
},
{
"type": "device_group",
"relations": {
"it_admin": {
"this": {}
},
"security_guard": {
"this": {}
}
}
}
]
}
info
Notice that any reference to the direct relationship keyword (self
in the friendly Syntax - or this
in the api syntax) has been removed. That indicates that a user cannot have a direct relationship with an object in this type.
With this change, anne
can no longer have a live_video_viewer
permission for device:1
except through having a security_guard
or it_admin
role first, and when she loses access to that role, she will automatically lose access to the live_video_viewer
permission.
Verification
Now that direct relationship is denied, we should see that anne
has live_video_viewer
relation to device:1
solely based on her position as security_guard
to device:1
. Let's find out.
To test this, we can add a new user emily
. Emily is not a security_guard
nor an it_admin
. However, we attempt to access via direct relations by adding the following relationship tuples:
- 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: 'emily', relation: 'live_video_viewer', object: 'device:1'},
{ user: 'emily', relation: 'recorded_video_viewer', object: 'device:1'},
{ user: 'emily', relation: 'device_renamer', object: 'device:1'}
]
}
});
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("emily"),
Relation: fgaSdk.PtrString("live_video_viewer"),
Object: fgaSdk.PtrString("device:1"),
},
{
User: fgaSdk.PtrString("emily"),
Relation: fgaSdk.PtrString("recorded_video_viewer"),
Object: fgaSdk.PtrString("device:1"),
},
{
User: fgaSdk.PtrString("emily"),
Relation: fgaSdk.PtrString("device_renamer"),
Object: fgaSdk.PtrString("device:1"),
},
},
},
}
_, 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 = "emily", Relation = "live_video_viewer", Object = "device:1" },
new() { User = "emily", Relation = "recorded_video_viewer", Object = "device:1" },
new() { User = "emily", Relation = "device_renamer", Object = "device:1" }
})
});
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":"emily","relation":"live_video_viewer","object":"device:1"},{"user":"emily","relation":"recorded_video_viewer","object":"device:1"},{"user":"emily","relation":"device_renamer","object":"device:1"}] }}'
write([
{
"user":"emily",
"relation":"live_video_viewer",
"object":"device:1"
},
{
"user":"emily",
"relation":"recorded_video_viewer",
"object":"device:1"
},
{
"user":"emily",
"relation":"device_renamer",
"object":"device:1"
}
])
Now try to query is emily related to device:1 as live_video_viewer?
. The returned result should be emily is not related to device:1 as live_video_viewer
. This confirms that direct relations have no effect on the live_video_viewer
relations, and that is because the direct relationship keyword (self
) was removed from the relation configuration.
- 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: 'emily',
relation: 'live_video_viewer',
object: 'device:1',
},});
// allowed = false
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("emily"),
Relation: fgaSdk.PtrString("live_video_viewer"),
Object: fgaSdk.PtrString("device:1"),
},
data, response, err := fgaClient.Auth0FgaApi.Check(context.Background()).Body(body).Execute()
// data = { allowed: false }
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 = "emily",
Relation = "live_video_viewer",
Object = "device:1"
});
// response.Allowed = false
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":"emily","relation":"live_video_viewer","object":"device:1"}}'
# Response: {"allowed":false}
check(
"emily", // check if the user `emily`
"live_video_viewer", // has an `live_video_viewer` relation
"device:1", // with the object `device:1`
);
Reply: false
is emily related to device:1 as live_video_viewer?
# Response: A red object indicating that the response from the API is `{"allowed":false}`
Query on the other relationships and you will see:
User | Object | Relation | Query | Relation? |
---|---|---|---|---|
emily | device:1 | recorded_video_viewer | is emily related to device:1 as recorded_video_viewer? | No |
emily | device:1 | device_renamer | is emily related to device:1 as device_renamer? | No |
Summary
In this post, you were introduced to fine grain authentication and Auth0 Fine Grained Authorization (FGA).
Upcoming posts will dive deeper into Auth0 Fine Grained Authorization (FGA), introducing concepts that will improve on the model you built today, and tackling more complex permission systems, with more relations and requirements that need to be met.
Exercises for You
- Try adding a second group tied to devices 4 and 5. Add only Charles and Dianne to this group, then try to run queries that would validate your model.
- Management has decided that Security Guards can only access live videos, and instituted a new position called Security Officer who can view both live and recorded videos. Can you update the authorization model to reflect that?