Modeling Authorization for Slack 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 a communication platform like Slack using Auth0 FGA.
- How to indicate relationships between a group of users and an object.
Used here to indicate that all members of a slack workspace can write in a certain channel.
See Modeling User Groups for more. - How to Model concentric relationship to have a certain relation on an object imply another relation on the same object.
Used here to indicate that legacy admins have all the permissions of the more granular channels admin.
See Modeling Concentric Relationships for more. - How to use the union operator condition to indicate that a user might have a certain relation with an object if they match any of the criteria indicated.
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 →
Concepts & Configuration Language
What You Will You Be Modeling
Slack is a messaging app for businesses that connects people to the information they need. By bringing people together to work as one unified team, Slack transforms the way organizations communicate. (Source: What is Slack?)
In this tutorial, you will build a subset of the Slack permission model (detailed below) in Auth0 Fine Grained Authorization (FGA), using some scenarios to validate the model.
As reference, you can refer to Slack's publicly available docs:
Note: For brevity, this tutorial will not model all of Slack's permissions. Instead, it will focus on modeling the scenarios outlined below.
Requirements
This tutorial will focus on the following sections (this is a partial list of Slack's roles):
Workspace Roles:
- Guest: This type of user is limited in their ability to use Slack, and is only permitted to see one or multiple delegated channels.
- Member: This is the base type of user that does not have any particular administrative abilities, but has basic access to the organization's Slack workspaces. When an administrative change needs to be made, these users need the support of admins and owners to make the changes.
- Legacy Admin: This type of user is the basic administrator of any organization, and can make a wide variety of administrative changes across Slack, such as renaming channels, archiving channels, setting up preferences and policies, inviting new users, and installing applications. Users with this role perform the majority of administrative tasks across a team.
System Roles:
- Channels Admin: This type of user has the permission to archive channels, rename channels, create private channels, and convert public channels into private channels.
Channel Settings:
- Visibility:
- Public: Visible to all members and open to join
- Private: Visible to admins and invited members
- Posting Permissions:
- Open: Anyone can post
- Limited: Only allowed members can post
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:
- Amy
- Bob
- Catherine
- David
- Emily
These users will interact in the following scenarios:
- You will assume there is a Slack workspace called Sandcastle
- Amy is a legacy admin of the Sandcastle workspace
- Bob is a member of the Sandcastle workspace with a channels admin role (Read more about system roles at Slack here)
- Catherine and Emily are normal members of the Sandcastle workspace, they can view all public channels, as well as channels they have been invited to
- David is a guest user with only view and write access to #proj-marketing-campaign, one of the public channels in the Sandcastle workspace
- Bob and Emily are in a private channel #marketing-internal in the Sandcastle workspace which only they can view and post to
- All members of the Sandcastle workspace can view the general channel, but only Amy and Emily can post to it
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 Workspaces & Channels
The goal by the end of this post is to ask Auth0 Fine Grained Authorization (FGA): Does person X have permission to perform action Y on channel Z? In response, you want to either get a confirmation that person X can indeed do that, or a rejection that they cannot. E.g. does David have access to view #general?
The Auth0 Fine Grained Authorization (FGA) is based on Zanzibar, a Relation Based Access Control system. This means it relies on objects and user relations to perform authorization checks.
Setting aside the permissions, you will start with the roles and learn how to express the requirements in terms of relations you can feed into Auth0 FGA.
The requirements stated:
- Amy is a legacy admin of the Sandcastle workspace
- Bob is a channels admin of the Sandcastle workspace
- Catherine and Emily are a normal members of the Sandcastle workspace
- David is a guest user
Here is how you would express than in Auth0 FGA'sauthorization model: You have a type called "workspace", and users can be related to it as a legacy_admin, channels_admin, member and guest
- DSL
- JSON
type workspace
relations
define legacy_admin as self
define channels_admin as self
define member as self
define guest as self
{
"type": "workspace",
"relations": {
"legacy_admin": {
"this": {}
},
"channels_admin": {
"this": {}
},
"member": {
"this": {}
},
"guest": {
"this": {}
}
}
}
info
Objects of type workspace
have users related to them as:
- Legacy Admin (
legacy_admin
) - Channels Admin (
channels_admin
) - Member (
member
) - Guest (
guest
)
self
(or this
in the API Syntax) is the direct relationship keyword and indicates that a user can have a direct relationship with an object of the type the relation specifies.
01. Individual permissions
To keep things simple and focus on Auth0 FGA rather than Slack complexity, we will model only four roles (legacy_admin, channels_admin, member, guest).
At the end of this section we want to have the following permissions represented
User | Relation | Object |
---|---|---|
amy | legacy_admin | workspace:sandcastle |
bob | channels_admin | workspace:sandcastle |
catherine | member | workspace:sandcastle |
david | guest | workspace:sandcastle |
emily | member | workspace:sandcastle |
To represent permissions in Auth0 FGA we use relations. For workspace permissions we need to create the following authorization model:
- DSL
- JSON
type workspace
relations
define legacy_admin as self
define channels_admin as self
define member as self
define guest as self
{
"type_definitions": [
{
"type": "workspace",
"relations": {
"legacy_admin": {
"this": {}
},
"channels_admin": {
"this": {}
},
"member": {
"this": {}
},
"guest": {
"this": {}
}
}
}
]
}
The Auth0 Fine Grained Authorization (FGA) service determines if a user has access to an object by checking if the user has a relation to that object. Let us examine one of those relations in detail:
- DSL
- JSON
type workspace
relations
define member as self
{
"type": "workspace",
"relations": {
"member": {
"this": {}
}
}
}
info
The snippet above indicates that objects of type workspace have users related to them as "member" if those users belong to the userset of all users related to the workspace as "member".
This means that a user can be directly related as a member to an object of type "workspace"
If we want to say amy
is a legacy_admin
of workspace:sandcastle
we create this relationship tuple
- 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: [
// Amy is a Legacy Admin in the Sandcastle workspace
{ user: 'amy', relation: 'legacy_admin', object: 'workspace:sandcastle'}
]
}
});
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 {
{
// Amy is a Legacy Admin in the Sandcastle workspace
User: fgaSdk.PtrString("amy"),
Relation: fgaSdk.PtrString("legacy_admin"),
Object: fgaSdk.PtrString("workspace:sandcastle"),
},
},
},
}
_, 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>() {
// Amy is a Legacy Admin in the Sandcastle workspace
new() { User = "amy", Relation = "legacy_admin", Object = "workspace:sandcastle" }
})
});
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":"amy","relation":"legacy_admin","object":"workspace:sandcastle"}] }}'
write([
// Amy is a Legacy Admin in the Sandcastle workspace
{
"user":"amy",
"relation":"legacy_admin",
"object":"workspace:sandcastle"
}
])
We can now ask Auth0 FGA "is amy
a legacy_admin of workspace:sandcastle?"
- 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: 'amy',
relation: 'legacy_admin',
object: 'workspace:sandcastle',
},});
// 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("amy"),
Relation: fgaSdk.PtrString("legacy_admin"),
Object: fgaSdk.PtrString("workspace:sandcastle"),
},
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 = "amy",
Relation = "legacy_admin",
Object = "workspace:sandcastle"
});
// 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":"amy","relation":"legacy_admin","object":"workspace:sandcastle"}}'
# Response: {"allowed":true}
check(
"amy", // check if the user `amy`
"legacy_admin", // has an `legacy_admin` relation
"workspace:sandcastle", // with the object `workspace:sandcastle`
);
Reply: true
is amy related to workspace:sandcastle as legacy_admin?
# Response: A green path from the user to the object indicating that the response from the API is `{"allowed":true}`
We can also say that catherine
is a member
of workspace:sandcastle
:
- 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: [
// Catherine is a Member in the Sandcastle workspace
{ user: 'catherine', relation: 'member', object: 'workspace:sandcastle'}
]
}
});
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 {
{
// Catherine is a Member in the Sandcastle workspace
User: fgaSdk.PtrString("catherine"),
Relation: fgaSdk.PtrString("member"),
Object: fgaSdk.PtrString("workspace:sandcastle"),
},
},
},
}
_, 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>() {
// Catherine is a Member in the Sandcastle workspace
new() { User = "catherine", Relation = "member", Object = "workspace:sandcastle" }
})
});
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":"catherine","relation":"member","object":"workspace:sandcastle"}] }}'
write([
// Catherine is a Member in the Sandcastle workspace
{
"user":"catherine",
"relation":"member",
"object":"workspace:sandcastle"
}
])
And verify by asking Auth0 FGA
- 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: 'catherine',
relation: 'member',
object: 'workspace:sandcastle',
},});
// 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("catherine"),
Relation: fgaSdk.PtrString("member"),
Object: fgaSdk.PtrString("workspace:sandcastle"),
},
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 = "catherine",
Relation = "member",
Object = "workspace:sandcastle"
});
// 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":"catherine","relation":"member","object":"workspace:sandcastle"}}'
# Response: {"allowed":true}
check(
"catherine", // check if the user `catherine`
"member", // has an `member` relation
"workspace:sandcastle", // with the object `workspace:sandcastle`
);
Reply: true
is catherine related to workspace:sandcastle as member?
# Response: A green path from the user to the object indicating that the response from the API is `{"allowed":true}`
Catherine, on the other hand, is not a legacy_admin of workspace:sandcastle.
- 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: 'catherine',
relation: 'legacy_admin',
object: 'workspace:sandcastle',
},});
// 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("catherine"),
Relation: fgaSdk.PtrString("legacy_admin"),
Object: fgaSdk.PtrString("workspace:sandcastle"),
},
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 = "catherine",
Relation = "legacy_admin",
Object = "workspace:sandcastle"
});
// 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":"catherine","relation":"legacy_admin","object":"workspace:sandcastle"}}'
# Response: {"allowed":false}
check(
"catherine", // check if the user `catherine`
"legacy_admin", // has an `legacy_admin` relation
"workspace:sandcastle", // with the object `workspace:sandcastle`
);
Reply: false
is catherine related to workspace:sandcastle as legacy_admin?
# Response: A red object indicating that the response from the API is `{"allowed":false}`
Repeat this process for the other relationships
[
{
// Bob is a Channels Admin in the Sandcastle workspace
"user": "bob",
"relation": "channels_admin",
"object": "workspace:sandcastle"
},
{
// David is a guest in the Sandcastle workspace
"user": "david",
"relation": "guest",
"object": "workspace:sandcastle"
},
{
// Emily is a Member in the Sandcastle workspace
"user": "emily",
"relation": "member",
"object": "workspace:sandcastle"
}
]
- 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: [
// Bob is a Channels Admin in the Sandcastle workspace
{ user: 'bob', relation: 'channels_admin', object: 'workspace:sandcastle'},
// David is a guest in the Sandcastle workspace
{ user: 'david', relation: 'guest', object: 'workspace:sandcastle'},
// Emily is a Member in the Sandcastle workspace
{ user: 'emily', relation: 'member', object: 'workspace:sandcastle'}
]
}
});
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 {
{
// Bob is a Channels Admin in the Sandcastle workspace
User: fgaSdk.PtrString("bob"),
Relation: fgaSdk.PtrString("channels_admin"),
Object: fgaSdk.PtrString("workspace:sandcastle"),
},
{
// David is a guest in the Sandcastle workspace
User: fgaSdk.PtrString("david"),
Relation: fgaSdk.PtrString("guest"),
Object: fgaSdk.PtrString("workspace:sandcastle"),
},
{
// Emily is a Member in the Sandcastle workspace
User: fgaSdk.PtrString("emily"),
Relation: fgaSdk.PtrString("member"),
Object: fgaSdk.PtrString("workspace:sandcastle"),
},
},
},
}
_, 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>() {
// Bob is a Channels Admin in the Sandcastle workspace
new() { User = "bob", Relation = "channels_admin", Object = "workspace:sandcastle" },
// David is a guest in the Sandcastle workspace
new() { User = "david", Relation = "guest", Object = "workspace:sandcastle" },
// Emily is a Member in the Sandcastle workspace
new() { User = "emily", Relation = "member", Object = "workspace:sandcastle" }
})
});
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":"bob","relation":"channels_admin","object":"workspace:sandcastle"},{"user":"david","relation":"guest","object":"workspace:sandcastle"},{"user":"emily","relation":"member","object":"workspace:sandcastle"}] }}'
write([
// Bob is a Channels Admin in the Sandcastle workspace
{
"user":"bob",
"relation":"channels_admin",
"object":"workspace:sandcastle"
},
// David is a guest in the Sandcastle workspace
{
"user":"david",
"relation":"guest",
"object":"workspace:sandcastle"
},
// Emily is a Member in the Sandcastle workspace
{
"user":"emily",
"relation":"member",
"object":"workspace:sandcastle"
}
])
Verification
To verify, we can issue check request to verify it is working as expected.
- 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: 'amy',
relation: 'legacy_admin',
object: 'workspace:sandcastle',
},});
// 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("amy"),
Relation: fgaSdk.PtrString("legacy_admin"),
Object: fgaSdk.PtrString("workspace:sandcastle"),
},
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 = "amy",
Relation = "legacy_admin",
Object = "workspace:sandcastle"
});
// 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":"amy","relation":"legacy_admin","object":"workspace:sandcastle"}}'
# Response: {"allowed":true}
check(
"amy", // check if the user `amy`
"legacy_admin", // has an `legacy_admin` relation
"workspace:sandcastle", // with the object `workspace:sandcastle`
);
Reply: true
is amy related to workspace:sandcastle as legacy_admin?
# Response: A green path from the user to the object indicating that the response from the API is `{"allowed":true}`
Let's try to verify the followings:
User | Object | Relation | Query | Relation? |
---|---|---|---|---|
amy | workspace:sandcastle | legacy_admin | is amy related to workspace:sandcastle as legacy_admin? | Yes |
david | workspace:sandcastle | legacy_admin | is david related to workspace:sandcastle as legacy_admin? | No |
amy | workspace:sandcastle | guest | is amy related to workspace:sandcastle as guest? | No |
david | workspace:sandcastle | guest | is david related to workspace:sandcastle as guest? | Yes |
amy | workspace:sandcastle | member | is amy related to workspace:sandcastle as member? | No |
david | workspace:sandcastle | member | is david related to workspace:sandcastle as member? | No |
02. Updating the workspace
authorization model with Implied Relations
Some of the queries that you ran earlier, while returning the correct response, do not match reality. One of which is:
- 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: 'amy',
relation: 'member',
object: 'workspace:sandcastle',
},});
// 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("amy"),
Relation: fgaSdk.PtrString("member"),
Object: fgaSdk.PtrString("workspace:sandcastle"),
},
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 = "amy",
Relation = "member",
Object = "workspace:sandcastle"
});
// 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":"amy","relation":"member","object":"workspace:sandcastle"}}'
# Response: {"allowed":false}
check(
"amy", // check if the user `amy`
"member", // has an `member` relation
"workspace:sandcastle", // with the object `workspace:sandcastle`
);
Reply: false
is amy related to workspace:sandcastle as member?
# Response: A red object indicating that the response from the API is `{"allowed":false}`
As you saw before, running this query will return amy is not a member of workspace:sandcastle
, which is correct based on the data you have given Auth0 Fine Grained Authorization (FGA) so far. But in reality, Amy, who is a legacy_admin
already has an implied channels_admin
and member
relations. In fact anyone (other than a guest) is a member
of the workspace.
To change this behavior, we will update our system with a concentric relationship model.
With the following updated authorization model, you are informing Auth0 Fine Grained Authorization (FGA) that any user who is related to a workspace as legacy_admin
, is also related as a channels_admin
and a member
.
- DSL
- JSON
type workspace
relations
define legacy_admin as self
define channels_admin as self or legacy_admin
define member as self or channels_admin or legacy_admin
define guest as self
{
"type_definitions": [
{
"type": "workspace",
"relations": {
"legacy_admin": {
"this": {}
},
"channels_admin": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "legacy_admin"
}
}
]
}
},
"member": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "channels_admin"
}
},
{
"computedUserset": {
"relation": "legacy_admin"
}
}
]
}
},
"guest": {
"this": {}
}
}
}
]
}
We can then verify amy
is a member
of workspace:sandcastle
.
- 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: 'amy',
relation: 'member',
object: 'workspace:sandcastle',
},});
// 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("amy"),
Relation: fgaSdk.PtrString("member"),
Object: fgaSdk.PtrString("workspace:sandcastle"),
},
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 = "amy",
Relation = "member",
Object = "workspace:sandcastle"
});
// 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":"amy","relation":"member","object":"workspace:sandcastle"}}'
# Response: {"allowed":true}
check(
"amy", // check if the user `amy`
"member", // has an `member` relation
"workspace:sandcastle", // with the object `workspace:sandcastle`
);
Reply: true
is amy related to workspace:sandcastle as member?
# Response: A green path from the user to the object indicating that the response from the API is `{"allowed":true}`
We can check for other users and relationships.
User | Object | Relation | Query | Relation? |
---|---|---|---|---|
amy | workspace:sandcastle | legacy_admin | is amy related to workspace:sandcastle as legacy_admin? | Yes |
david | workspace:sandcastle | legacy_admin | is david related to workspace:sandcastle as legacy_admin? | No |
amy | workspace:sandcastle | guest | is amy related to workspace:sandcastle as guest? | No |
david | workspace:sandcastle | guest | is david related to workspace:sandcastle as guest? | Yes |
amy | workspace:sandcastle | member | is amy related to workspace:sandcastle as member? | Yes |
david | workspace:sandcastle | member | is david related to workspace:sandcastle as member? | No |
03. Updating the Authorization Model to include Channels
So far, you have modeled the users' relations to the workspace itself. In this task you will expand the model to include the relations concerning the channels.
By the end of it, you will run some queries to check whether a user can view or write to a certain channel. Queries such as:
is david related to channel:general as viewer?
(expected answer: No relation, as David is a guest user with only a relation to #proj-marketing-campaign)is david related to channel:proj_marketing_campaign as viewer?
(expected answer: There is a relation, as there is a relation between David and #proj-marketing-campaign as a writer)is bob related to channel:general as viewer?
(expected answer: There is a relation, as Bob is a member of the Sandcastle workspace, and all members of the workspace have a viewer relation to #general)
The requirements are:
- Amy, Bob, Catherine and Emily, are normal members of the Sandcastle workspace, they can view all public channels, in this case: #general and #proj-marketing-campaign
- David, a guest user, has only view and write access to the #proj-marketing-campaign channel
- Bob and Emily are the only ones with either view or write access to the #marketing-internal channel
- Amy and Emily are the only ones with write access to the #general channel
The possible relations to channels are:
- Workspace includes the channel, consider the relation that of a parent workspace
- A user can be a viewer and/or writer on a channel
The authorization model already has a section describing the workspace, what remains is describing the channel. That can be done by adding the following section to the configuration above:
- DSL
- JSON
type channel
relations
define parent_workspace as self
define writer as self
define viewer as self
{
"type": "channel",
"relations": {
"parent_workspace": {
"this": {}
},
"writer": {
"this": {}
},
"viewer": {
"this": {}
}
}
}
info
The configuration snippet above describes a channel that can have the following relations:
- workspaces related to it as
parent_workspace
- users related to it as
writer
- users related to it as
viewer
Implied relation
There is an implied relation that anyone who can write to a channel can also read from it, so the authorization model can be modified to be:
- DSL
- JSON
type channel
relations
define parent_workspace as self
define writer as self
define viewer as self or writer
{
"type": "channel",
"relations": {
"parent_workspace": {
"this": {}
},
"writer": {
"this": {}
},
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "writer"
}
}
]
}
}
}
}
info
Note that the channel type definition has been updated to indicate that viewer is the union of:
- the set of users with a direct viewer relation to this object
- the set of users with writer relations to this object
As a result, the authorization model is:
- DSL
- JSON
type workspace
relations
define legacy_admin as self
define channels_admin as self or legacy_admin
define member as self or channels_admin or legacy_admin
define guest as self
type channel
relations
define parent_workspace as self
define writer as self
define viewer as self or writer
{
"type_definitions": [
{
"type": "workspace",
"relations": {
"legacy_admin": {
"this": {}
},
"channels_admin": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "legacy_admin"
}
}
]
}
},
"member": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "channels_admin"
}
},
{
"computedUserset": {
"relation": "legacy_admin"
}
}
]
}
},
"guest": {
"this": {}
}
}
},
{
"type": "channel",
"relations": {
"parent_workspace": {
"this": {}
},
"writer": {
"this": {}
},
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"relation": "writer"
}
}
]
}
}
}
}
]
}
Updating relationship tuples
What remains is to add the relationship tuples to indicate the relation between the users, workspace and the channels.
The Sandcastle workspace is a parent workspace of the #general, #marketing-internal and #proj-marketing-campaign channels.
- 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: 'workspace:sandcastle', relation: 'parent_workspace', object: 'channel:general'},
{ user: 'workspace:sandcastle', relation: 'parent_workspace', object: 'channel:marketing_internal'},
{ user: 'workspace:sandcastle', relation: 'parent_workspace', object: 'channel:proj_marketing_campaign'}
]
}
});
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("workspace:sandcastle"),
Relation: fgaSdk.PtrString("parent_workspace"),
Object: fgaSdk.PtrString("channel:general"),
},
{
User: fgaSdk.PtrString("workspace:sandcastle"),
Relation: fgaSdk.PtrString("parent_workspace"),
Object: fgaSdk.PtrString("channel:marketing_internal"),
},
{
User: fgaSdk.PtrString("workspace:sandcastle"),
Relation: fgaSdk.PtrString("parent_workspace"),
Object: fgaSdk.PtrString("channel:proj_marketing_campaign"),
},
},
},
}
_, 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 = "workspace:sandcastle", Relation = "parent_workspace", Object = "channel:general" },
new() { User = "workspace:sandcastle", Relation = "parent_workspace", Object = "channel:marketing_internal" },
new() { User = "workspace:sandcastle", Relation = "parent_workspace", Object = "channel:proj_marketing_campaign" }
})
});
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":"workspace:sandcastle","relation":"parent_workspace","object":"channel:general"},{"user":"workspace:sandcastle","relation":"parent_workspace","object":"channel:marketing_internal"},{"user":"workspace:sandcastle","relation":"parent_workspace","object":"channel:proj_marketing_campaign"}] }}'
write([
{
"user":"workspace:sandcastle",
"relation":"parent_workspace",
"object":"channel:general"
},
{
"user":"workspace:sandcastle",
"relation":"parent_workspace",
"object":"channel:marketing_internal"
},
{
"user":"workspace:sandcastle",
"relation":"parent_workspace",
"object":"channel:proj_marketing_campaign"
}
])
#general
channel
The #general
channel is a public channel visible to all the members of the workspace. In Auth0 FGA, you represent this relation in the form of the following relationship tuple:
- 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: [
// The set of users related to `workspace:sandcastle` as member are also related to `channel:general` as `viewer`
{ user: 'workspace:sandcastle#member', relation: 'viewer', object: 'channel:general'}
]
}
});
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 {
{
// The set of users related to `workspace:sandcastle` as member are also related to `channel:general` as `viewer`
User: fgaSdk.PtrString("workspace:sandcastle#member"),
Relation: fgaSdk.PtrString("viewer"),
Object: fgaSdk.PtrString("channel:general"),
},
},
},
}
_, 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>() {
// The set of users related to `workspace:sandcastle` as member are also related to `channel:general` as `viewer`
new() { User = "workspace:sandcastle#member", Relation = "viewer", Object = "channel:general" }
})
});
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":"workspace:sandcastle#member","relation":"viewer","object":"channel:general"}] }}'
write([
// The set of users related to `workspace:sandcastle` as member are also related to `channel:general` as `viewer`
{
"user":"workspace:sandcastle#member",
"relation":"viewer",
"object":"channel:general"
}
])
info
This indicates The set of users related to workspace:sandcastle
as member are also related to channel:general
as viewer
And to indicate that Amy and Emily can write to it:
- 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: [
// Due to the configuration update you added earlier, writer relation is enough to imply a viewer relation
{ user: 'amy', relation: 'writer', object: 'channel:general'},
{ user: 'emily', relation: 'writer', object: 'channel:general'}
]
}
});
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 {
{
// Due to the configuration update you added earlier, writer relation is enough to imply a viewer relation
User: fgaSdk.PtrString("amy"),
Relation: fgaSdk.PtrString("writer"),
Object: fgaSdk.PtrString("channel:general"),
},
{
User: fgaSdk.PtrString("emily"),
Relation: fgaSdk.PtrString("writer"),
Object: fgaSdk.PtrString("channel:general"),
},
},
},
}
_, 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>() {
// Due to the configuration update you added earlier, writer relation is enough to imply a viewer relation
new() { User = "amy", Relation = "writer", Object = "channel:general" },
new() { User = "emily", Relation = "writer", Object = "channel:general" }
})
});
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":"amy","relation":"writer","object":"channel:general"},{"user":"emily","relation":"writer","object":"channel:general"}] }}'
write([
// Due to the configuration update you added earlier, writer relation is enough to imply a viewer relation
{
"user":"amy",
"relation":"writer",
"object":"channel:general"
},
{
"user":"emily",
"relation":"writer",
"object":"channel:general"
}
])
#marketing-internal
channel
The #marketing-internal
is visible to only Bob and Emily. They can view and write in it.
- 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: 'bob', relation: 'writer', object: 'channel:marketing_internal'},
{ user: 'emily', relation: 'writer', object: 'channel:marketing_internal'}
]
}
});
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("bob"),
Relation: fgaSdk.PtrString("writer"),
Object: fgaSdk.PtrString("channel:marketing_internal"),
},
{
User: fgaSdk.PtrString("emily"),
Relation: fgaSdk.PtrString("writer"),
Object: fgaSdk.PtrString("channel:marketing_internal"),
},
},
},
}
_, 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 = "bob", Relation = "writer", Object = "channel:marketing_internal" },
new() { User = "emily", Relation = "writer", Object = "channel:marketing_internal" }
})
});
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":"bob","relation":"writer","object":"channel:marketing_internal"},{"user":"emily","relation":"writer","object":"channel:marketing_internal"}] }}'
write([
{
"user":"bob",
"relation":"writer",
"object":"channel:marketing_internal"
},
{
"user":"emily",
"relation":"writer",
"object":"channel:marketing_internal"
}
])
#proj-marketing-campaign
channel
The #proj-marketing-campaign
is public to all members of the Sandcastle workspace. They can view and write in it.
- 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: 'workspace:sandcastle#member', relation: 'writer', object: 'channel:proj_marketing_campaign'}
]
}
});
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("workspace:sandcastle#member"),
Relation: fgaSdk.PtrString("writer"),
Object: fgaSdk.PtrString("channel:proj_marketing_campaign"),
},
},
},
}
_, 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 = "workspace:sandcastle#member", Relation = "writer", Object = "channel:proj_marketing_campaign" }
})
});
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":"workspace:sandcastle#member","relation":"writer","object":"channel:proj_marketing_campaign"}] }}'
write([
{
"user":"workspace:sandcastle#member",
"relation":"writer",
"object":"channel:proj_marketing_campaign"
}
])
David is a guest user who can also view and write to #proj-marketing-campaign
- 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: 'david', relation: 'writer', object: 'channel:proj_marketing_campaign'}
]
}
});
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("david"),
Relation: fgaSdk.PtrString("writer"),
Object: fgaSdk.PtrString("channel:proj_marketing_campaign"),
},
},
},
}
_, 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 = "david", Relation = "writer", Object = "channel:proj_marketing_campaign" }
})
});
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":"david","relation":"writer","object":"channel:proj_marketing_campaign"}] }}'
write([
{
"user":"david",
"relation":"writer",
"object":"channel:proj_marketing_campaign"
}
])
Verification
Now that you have added the necessary relationship tuples, you will check to make sure that your configuration is valid.
First, we want to ensure david is not related to channel:general as 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: 'david',
relation: 'viewer',
object: 'channel:general',
},});
// 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("david"),
Relation: fgaSdk.PtrString("viewer"),
Object: fgaSdk.PtrString("channel:general"),
},
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 = "david",
Relation = "viewer",
Object = "channel:general"
});
// 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":"david","relation":"viewer","object":"channel:general"}}'
# Response: {"allowed":false}
check(
"david", // check if the user `david`
"viewer", // has an `viewer` relation
"channel:general", // with the object `channel:general`
);
Reply: false
is david related to channel:general as viewer?
# Response: A red object indicating that the response from the API is `{"allowed":false}`
David should be related to channel:proj_marketing_campaign as 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: 'david',
relation: 'viewer',
object: 'channel:proj_marketing_campaign',
},});
// 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("david"),
Relation: fgaSdk.PtrString("viewer"),
Object: fgaSdk.PtrString("channel:proj_marketing_campaign"),
},
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 = "david",
Relation = "viewer",
Object = "channel:proj_marketing_campaign"
});
// 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":"david","relation":"viewer","object":"channel:proj_marketing_campaign"}}'
# Response: {"allowed":true}
check(
"david", // check if the user `david`
"viewer", // has an `viewer` relation
"channel:proj_marketing_campaign", // with the object `channel:proj_marketing_campaign`
);
Reply: true
is david related to channel:proj_marketing_campaign as viewer?
# Response: A green path from the user to the object indicating that the response from the API is `{"allowed":true}`
Repeat this for the following relations
User | Object | Relation | Query | Relation? |
---|---|---|---|---|
amy | workspace:sandcastle | legacy_admin | is amy related to workspace:sandcastle as legacy_admin? | Yes |
amy | workspace:sandcastle | member | is amy related to workspace:sandcastle as member? | Yes |
amy | workspace:sandcastle | channels_admin | is amy related to workspace:sandcastle as channels_admin? | Yes |
amy | channel:general | writer | is amy related to channel:general as writer? | Yes |
amy | channel:general | viewer | is amy related to channel:general as viewer? | Yes |
amy | channel:marketing_internal | writer | is amy related to channel:marketing_internal as writer? | No |
amy | channel:marketing_internal | viewer | is amy related to channel:marketing_internal as viewer? | No |
emily | channel:marketing_internal | writer | is emily related to channel:marketing_internal as writer? | Yes |
emily | channel:marketing_internal | viewer | is emily related to channel:marketing_internal as viewer? | Yes |
david | workspace:sandcastle | guest | is david related to workspace:sandcastle as guest? | Yes |
david | workspace:sandcastle | member | is david related to workspace:sandcastle as member? | No |
david | channel:general | viewer | is david related to channel:general as viewer? | No |
david | channel:marketing_internal | viewer | is david related to channel:marketing_internal as viewer? | No |
david | channel:proj_marketing_campaign | viewer | is david related to channel:proj_marketing_campaign as viewer? | Yes |
Summary
- Have a basic understanding of authorization and Auth0 FGA Concepts.
- Understand how to model authorization for a communication platform like Slack using Auth0 FGA.
In this tutorial, you:
- were introduced to fine grain authentication and Auth0 FGA.
- learned how to build and test an Auth0 Fine Grained Authorization (FGA) authorization model for a communication platforms like Slack.
Upcoming tutorials will dive deeper into Auth0 Fine Grained Authorization (FGA), introducing concepts that will improve on the model you built today, and tackling different permission systems, with other relations and requirements that need to be met.
If you are interested in learning more about Authorization and Role Management at Slack, check out the Auth0 Fine Grained Authorization (FGA) team's chat with the Slack engineering team.
Exercises for You
- Try adding more relationship tuples to represent other users and channels being added. Then run queries to make sure that the authorization model remains valid.
- Update the configuration to model more Slack permissions (workspace owners, Slack orgs), then add the relationship tuples necessary and run some queries to validate your configuration.