Authorization Through Organization Context
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 Okta FGA during the Developer Community Preview can be found here.
This section tackles cases where a user may have access to a particular resource through their presence in a particular organization, and they should have that access only when logged in within the context of that organization.
- An employee’s ability to access a document when they are connected to the organization VPN or the api call is originating from an internal IP address.
- A support engineer is only able to access a user's account during office hours.
- If a user belongs to multiple organizations, they are only able to access a resource if they set a specific organization in their current context.
Before You Start
To follow this guide, you should be familiar with some Okta FGA Concepts.
Okta FGA Concepts
- A Relation: is a string defined in the type definition of an authorization model that defines the possibility of a relationship between an object of the same type as the type definition and a user in the system
- A Check Request: is a call to the Okta FGA check endpoint that returns whether the user has a certain relationship with an object.
- A Relationship Tuple: a grouping consisting of a user, a relation and an object stored in Okta FGA
- A Contextual Tuple: a tuple that can be added to a check request, and only exist within the context of that particular request.
You also need to be familiar with:
- Modeling Object-to-Object Relationships: You need to know how to create relationships between objects and how that might affect a user's relationships to those objects. Learn more →
- Modeling Multiple Restrictions: You need to know how to model requiring multiple authorizations before allowing users to perform certain actions. Learn more →
Scenario
For the scope of this guide, we are going to consider the following scenario.
Consider you are building the authorization model for a multi-tenant project management system.
In this particular system:
- projects are owned and managed by companies
- users can be members of multiple companies
- project access is governed by the user's role in the organization that manages the project
In order for a user to access a project:
- The project needs to be managed by an organization the user is a member of
- A project is owned by a single organization
- A project can be shared with partner companies (that are able to view, edit but not perform admin actions, such as deletion, on the project)
- The user should have a role that grants access to the project
- The user should be logged in within the context of that organization
We will start with the following authorization model:
- DSL
- JSON
model
schema 1.1
type user
type organization
relations
define member: [user]
define project_manager: [user]
define project_editor: [user]
type project
relations
define owner: [organization]
define partner: [organization]
define manager: project_manager from owner
define editor: project_editor from owner or project_editor from partner or manager
define can_delete: manager
define can_edit: editor
define can_view: editor
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "organization",
"relations": {
"member": {
"this": {}
},
"project_manager": {
"this": {}
},
"project_editor": {
"this": {}
}
},
"metadata": {
"relations": {
"member": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"project_manager": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"project_editor": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
},
{
"type": "project",
"relations": {
"owner": {
"this": {}
},
"partner": {
"this": {}
},
"manager": {
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "owner"
},
"computedUserset": {
"object": "",
"relation": "project_manager"
}
}
},
"editor": {
"union": {
"child": [
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "owner"
},
"computedUserset": {
"object": "",
"relation": "project_editor"
}
}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "partner"
},
"computedUserset": {
"object": "",
"relation": "project_editor"
}
}
},
{
"computedUserset": {
"object": "",
"relation": "manager"
}
}
]
}
},
"can_delete": {
"computedUserset": {
"object": "",
"relation": "manager"
}
},
"can_edit": {
"computedUserset": {
"object": "",
"relation": "editor"
}
},
"can_view": {
"computedUserset": {
"object": "",
"relation": "editor"
}
}
},
"metadata": {
"relations": {
"owner": {
"directly_related_user_types": [
{
"type": "organization"
}
]
},
"partner": {
"directly_related_user_types": [
{
"type": "organization"
}
]
}
}
}
}
]
}
We are considering the case that:
- Anne has a project manager role at organizations A, B and C
- Beth has a project manager role at organization B
- Carl has a project manager role at organization C
- Project X is owned by organization A
- Project X is shared with organization B
The above state translates to the following relationship tuples:
- Node.js
- Go
- .NET
- Python
- curl
- CLI
- Pseudocode
await fgaClient.write({
writes: [
// Anne has a `project manager` role at organization A
{ user: 'user:anne', relation: 'project_manager', object: 'organization:A'},
// Anne has a `project manager` role at organization B
{ user: 'user:anne', relation: 'project_manager', object: 'organization:B'},
// Anne has a `project manager` role at organization C
{ user: 'user:anne', relation: 'project_manager', object: 'organization:C'},
// Beth has a `project manager` role at organization B
{ user: 'user:anne', relation: 'project_manager', object: 'organization:B'},
// Carl has a `project manager` role at organization C
{ user: 'user:carl', relation: 'project_manager', object: 'organization:C'},
// Organization A owns Project X
{ user: 'organization:A', relation: 'owner', object: 'project:X'},
// Project X is shared with Organization B
{ user: 'organization:B', relation: 'partner', object: 'project:X'}]
},
}, {
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
options := ClientWriteOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := fgaClient.ClientWriteRequest{
Writes: &[]ClientTupleKey{
{
// Anne has a `project manager` role at organization A
User: openfga.PtrString("user:anne"),
Relation: openfga.PtrString("project_manager"),
Object: openfga.PtrString("organization:A"),
},
{
// Anne has a `project manager` role at organization B
User: openfga.PtrString("user:anne"),
Relation: openfga.PtrString("project_manager"),
Object: openfga.PtrString("organization:B"),
},
{
// Anne has a `project manager` role at organization C
User: openfga.PtrString("user:anne"),
Relation: openfga.PtrString("project_manager"),
Object: openfga.PtrString("organization:C"),
},
{
// Beth has a `project manager` role at organization B
User: openfga.PtrString("user:anne"),
Relation: openfga.PtrString("project_manager"),
Object: openfga.PtrString("organization:B"),
},
{
// Carl has a `project manager` role at organization C
User: openfga.PtrString("user:carl"),
Relation: openfga.PtrString("project_manager"),
Object: openfga.PtrString("organization:C"),
},
{
// Organization A owns Project X
User: openfga.PtrString("organization:A"),
Relation: openfga.PtrString("owner"),
Object: openfga.PtrString("project:X"),
},
{
// Project X is shared with Organization B
User: openfga.PtrString("organization:B"),
Relation: openfga.PtrString("partner"),
Object: openfga.PtrString("project:X"),
}, } }
data, err := fgaClient.Write(context.Background()).Body(requestBody).Options(options).Execute()
if err != nil {
// .. Handle error
}
var options = new ClientListObjectsOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientWriteRequest() {
Writes = new List<ClientTupleKey>() {
// Anne has a `project manager` role at organization A
new() { User = "user:anne", Relation = "project_manager", Object = "organization:A" },
// Anne has a `project manager` role at organization B
new() { User = "user:anne", Relation = "project_manager", Object = "organization:B" },
// Anne has a `project manager` role at organization C
new() { User = "user:anne", Relation = "project_manager", Object = "organization:C" },
// Beth has a `project manager` role at organization B
new() { User = "user:anne", Relation = "project_manager", Object = "organization:B" },
// Carl has a `project manager` role at organization C
new() { User = "user:carl", Relation = "project_manager", Object = "organization:C" },
// Organization A owns Project X
new() { User = "organization:A", Relation = "owner", Object = "project:X" },
// Project X is shared with Organization B
new() { User = "organization:B", Relation = "partner", Object = "project:X" }
},
};
var response = await fgaClient.Write(body, options);
options = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientWriteRequest(
writes=[
ClientTuple(
# Anne has a `project manager` role at organization A
user="user:anne",
relation="project_manager",
object="organization:A",
),
ClientTuple(
# Anne has a `project manager` role at organization B
user="user:anne",
relation="project_manager",
object="organization:B",
),
ClientTuple(
# Anne has a `project manager` role at organization C
user="user:anne",
relation="project_manager",
object="organization:C",
),
ClientTuple(
# Beth has a `project manager` role at organization B
user="user:anne",
relation="project_manager",
object="organization:B",
),
ClientTuple(
# Carl has a `project manager` role at organization C
user="user:carl",
relation="project_manager",
object="organization:C",
),
ClientTuple(
# Organization A owns Project X
user="organization:A",
relation="owner",
object="project:X",
),
ClientTuple(
# Project X is shared with Organization B
user="organization:B",
relation="partner",
object="project:X",
),
],
)
response = await fga_client.write(body, options)
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/write \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"writes": { "tuple_keys" : [{"user":"user:anne","relation":"project_manager","object":"organization:A"},{"user":"user:anne","relation":"project_manager","object":"organization:B"},{"user":"user:anne","relation":"project_manager","object":"organization:C"},{"user":"user:anne","relation":"project_manager","object":"organization:B"},{"user":"user:carl","relation":"project_manager","object":"organization:C"},{"user":"organization:A","relation":"owner","object":"project:X"},{"user":"organization:B","relation":"partner","object":"project:X"}] }, "authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"}'
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne project_manager organization:A
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne project_manager organization:B
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne project_manager organization:C
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne project_manager organization:B
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:carl project_manager organization:C
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw organization:A owner project:X
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw organization:B partner project:X
write([
// Anne has a `project manager` role at organization A
{
"user":"user:anne",
"relation":"project_manager",
"object":"organization:A"
},
// Anne has a `project manager` role at organization B
{
"user":"user:anne",
"relation":"project_manager",
"object":"organization:B"
},
// Anne has a `project manager` role at organization C
{
"user":"user:anne",
"relation":"project_manager",
"object":"organization:C"
},
// Beth has a `project manager` role at organization B
{
"user":"user:anne",
"relation":"project_manager",
"object":"organization:B"
},
// Carl has a `project manager` role at organization C
{
"user":"user:carl",
"relation":"project_manager",
"object":"organization:C"
},
// Organization A owns Project X
{
"user":"organization:A",
"relation":"owner",
"object":"project:X"
},
// Project X is shared with Organization B
{
"user":"organization:B",
"relation":"partner",
"object":"project:X"
}
], authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw")
Requirements
- When logging in within the context of organization A, Anne should be able to view and delete project X.
- When logging in within the context of organization B, Anne should be able to view, but not delete, project X.
- When logging in within the context of organization C, Anne should not be able to view nor delete project X.
- When logging in within the context of organization B, Beth should be able to view, but not delete, project X.
- Carl should not be able to view nor delete project X.
Step By Step
In order to solve for the requirements above, we will break the problem down into three steps:
- Understand relationships without contextual tuples. For example, we need to ensure that Anne can view and delete "Project X".
- Take organization context into consideration. This includes extending the authorization model and a temporary step of adding the required tuples to mark that Anne is in an approved context.
- Use contextual tuples for context related checks.
Understand Relationships Without Contextual Data
With the authorization model and relationship tuples shown above, Okta FGA has all the information needed to ensure that Anne can view and delete "Project X".
We can verify that using the following checks:
- Anne can view Project X
- Node.js
- Go
- .NET
- Python
- CLI
- curl
- Pseudocode
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:anne',
relation: 'can_view',
object: 'project:X',
}, {
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
});
// allowed = true
options := ClientCheckOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := ClientCheckRequest{
User: "user:anne",
Relation: "can_view",
Object: "project:X",
}
data, err := fgaClient.Check(context.Background()).Body(body).Options(options).Execute()
// data = { allowed: true }
var options = new ClientCheckOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientCheckRequest {
User = "user:anne",
Relation = "can_view",
Object = "project:X",
};
var response = await fgaClient.Check(body, options);
// response.Allowed = trueoptions = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientCheckRequest(
user="user:anne",
relation="can_view",
object="project:X",
)
response = await fga_client.check(body, options)
# response.allowed = truefga query check --store-id=$FGA_STORE_ID --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne can_view project:X
# Response: {"allowed":true}curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:anne","relation":"can_view","object":"project:X"}}'
# Response: {"allowed":true}check(
user = "user:anne", // check if the user `user:anne`
relation = "can_view", // has an `can_view` relation
object = "project:X", // with the object `project:X`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: true - Anne can delete Project X
- Node.js
- Go
- .NET
- Python
- CLI
- curl
- Pseudocode
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:anne',
relation: 'can_delete',
object: 'project:X',
}, {
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
});
// allowed = true
options := ClientCheckOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := ClientCheckRequest{
User: "user:anne",
Relation: "can_delete",
Object: "project:X",
}
data, err := fgaClient.Check(context.Background()).Body(body).Options(options).Execute()
// data = { allowed: true }
var options = new ClientCheckOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientCheckRequest {
User = "user:anne",
Relation = "can_delete",
Object = "project:X",
};
var response = await fgaClient.Check(body, options);
// response.Allowed = trueoptions = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientCheckRequest(
user="user:anne",
relation="can_delete",
object="project:X",
)
response = await fga_client.check(body, options)
# response.allowed = truefga query check --store-id=$FGA_STORE_ID --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne can_delete project:X
# Response: {"allowed":true}curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:anne","relation":"can_delete","object":"project:X"}}'
# Response: {"allowed":true}check(
user = "user:anne", // check if the user `user:anne`
relation = "can_delete", // has an `can_delete` relation
object = "project:X", // with the object `project:X`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: true
More checks
- Node.js
- Go
- .NET
- Python
- CLI
- curl
- Pseudocode
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:beth',
relation: 'can_view',
object: 'project:X',
}, {
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
});
// allowed = true
options := ClientCheckOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := ClientCheckRequest{
User: "user:beth",
Relation: "can_view",
Object: "project:X",
}
data, err := fgaClient.Check(context.Background()).Body(body).Options(options).Execute()
// data = { allowed: true }
var options = new ClientCheckOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientCheckRequest {
User = "user:beth",
Relation = "can_view",
Object = "project:X",
};
var response = await fgaClient.Check(body, options);
// response.Allowed = true
options = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientCheckRequest(
user="user:beth",
relation="can_view",
object="project:X",
)
response = await fga_client.check(body, options)
# response.allowed = true
fga query check --store-id=$FGA_STORE_ID --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:beth can_view project:X
# Response: {"allowed":true}
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:beth","relation":"can_view","object":"project:X"}}'
# Response: {"allowed":true}
check(
user = "user:beth", // check if the user `user:beth`
relation = "can_view", // has an `can_view` relation
object = "project:X", // with the object `project:X`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: true
- Node.js
- Go
- .NET
- Python
- CLI
- curl
- Pseudocode
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:beth',
relation: 'can_delete',
object: 'project:X',
}, {
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
});
// allowed = false
options := ClientCheckOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := ClientCheckRequest{
User: "user:beth",
Relation: "can_delete",
Object: "project:X",
}
data, err := fgaClient.Check(context.Background()).Body(body).Options(options).Execute()
// data = { allowed: false }
var options = new ClientCheckOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientCheckRequest {
User = "user:beth",
Relation = "can_delete",
Object = "project:X",
};
var response = await fgaClient.Check(body, options);
// response.Allowed = false
options = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientCheckRequest(
user="user:beth",
relation="can_delete",
object="project:X",
)
response = await fga_client.check(body, options)
# response.allowed = false
fga query check --store-id=$FGA_STORE_ID --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:beth can_delete project:X
# Response: {"allowed":false}
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:beth","relation":"can_delete","object":"project:X"}}'
# Response: {"allowed":false}
check(
user = "user:beth", // check if the user `user:beth`
relation = "can_delete", // has an `can_delete` relation
object = "project:X", // with the object `project:X`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: false
- Node.js
- Go
- .NET
- Python
- CLI
- curl
- Pseudocode
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:carl',
relation: 'can_view',
object: 'project:X',
}, {
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
});
// allowed = false
options := ClientCheckOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := ClientCheckRequest{
User: "user:carl",
Relation: "can_view",
Object: "project:X",
}
data, err := fgaClient.Check(context.Background()).Body(body).Options(options).Execute()
// data = { allowed: false }
var options = new ClientCheckOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientCheckRequest {
User = "user:carl",
Relation = "can_view",
Object = "project:X",
};
var response = await fgaClient.Check(body, options);
// response.Allowed = false
options = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientCheckRequest(
user="user:carl",
relation="can_view",
object="project:X",
)
response = await fga_client.check(body, options)
# response.allowed = false
fga query check --store-id=$FGA_STORE_ID --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:carl can_view project:X
# Response: {"allowed":false}
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:carl","relation":"can_view","object":"project:X"}}'
# Response: {"allowed":false}
check(
user = "user:carl", // check if the user `user:carl`
relation = "can_view", // has an `can_view` relation
object = "project:X", // with the object `project:X`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: false
- Node.js
- Go
- .NET
- Python
- CLI
- curl
- Pseudocode
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:carl',
relation: 'can_delete',
object: 'project:X',
}, {
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
});
// allowed = false
options := ClientCheckOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := ClientCheckRequest{
User: "user:carl",
Relation: "can_delete",
Object: "project:X",
}
data, err := fgaClient.Check(context.Background()).Body(body).Options(options).Execute()
// data = { allowed: false }
var options = new ClientCheckOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientCheckRequest {
User = "user:carl",
Relation = "can_delete",
Object = "project:X",
};
var response = await fgaClient.Check(body, options);
// response.Allowed = false
options = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientCheckRequest(
user="user:carl",
relation="can_delete",
object="project:X",
)
response = await fga_client.check(body, options)
# response.allowed = false
fga query check --store-id=$FGA_STORE_ID --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:carl can_delete project:X
# Response: {"allowed":false}
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:carl","relation":"can_delete","object":"project:X"}}'
# Response: {"allowed":false}
check(
user = "user:carl", // check if the user `user:carl`
relation = "can_delete", // has an `can_delete` relation
object = "project:X", // with the object `project:X`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: false
Note that so far, we have not prevented Anne from viewing "Project X" even if Anne is viewing it from the context of Organization C.
Take Organization Context Into Consideration
Extend The Authorization Model
In order to add a restriction based on the current organization context, we will make use of Okta FGA configuration language's support for intersection to specify that a user has to both have access and be in the correct context in order to be authorized.
We can do that by introducing some new relations and updating existing relation definitions:
- On the "organization" type
- Add "user_in_context" relation to mark that a user's access is being evaluated within that particular context
- Update the "project_manager" relation to require that the user be in the correct context (by adding
and user_in_context
to the relation definition) - Considering that Okta FGA does not yet support multiple logical operations within the same definition, we will split "project_editor" into two:
- "base_project_editor" editor which will contain the original relation definition (
[user] or project_manager
) - "project_editor" which will require that a user has both the "base_project_editor" and the "user_in_context" relations
- "base_project_editor" editor which will contain the original relation definition (
The "organization" type definition then becomes:
- DSL
- JSON
type organization
relations
define member: [user]
define project_manager: [user] and user_in_context
define base_project_editor: [user] or project_manager
define project_editor: base_project_editor and user_in_context
define user_in_context: [user]
{
"type": "organization",
"relations": {
"member": {
"this": {}
},
"project_manager": {
"intersection": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"object": "",
"relation": "user_in_context"
}
}
]
}
},
"base_project_editor": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"object": "",
"relation": "project_manager"
}
}
]
}
},
"project_editor": {
"intersection": {
"child": [
{
"computedUserset": {
"object": "",
"relation": "base_project_editor"
}
},
{
"computedUserset": {
"object": "",
"relation": "user_in_context"
}
}
]
}
},
"user_in_context": {
"this": {}
}
},
"metadata": {
"relations": {
"member": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"project_manager": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"base_project_editor": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"user_in_context": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
}
- On the "project" type
- Nothing will need to be done, as it will inherit the updated "project_manager" and "project_editor" relation definitions from "organization"
Add The Required Tuples To Mark That Anne Is In An Approved Context
Now that we have updated our authorization model to take the current user's organization context into consideration, you will notice that Anne has lost access because nothing indicates that Anne is authorizing from the context of an organization. You can verify that by issuing the following check:
- Node.js
- Go
- .NET
- Python
- CLI
- curl
- Pseudocode
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:anne',
relation: 'can_view',
object: 'project:X',
}, {
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
});
// allowed = false
options := ClientCheckOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := ClientCheckRequest{
User: "user:anne",
Relation: "can_view",
Object: "project:X",
}
data, err := fgaClient.Check(context.Background()).Body(body).Options(options).Execute()
// data = { allowed: false }
var options = new ClientCheckOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientCheckRequest {
User = "user:anne",
Relation = "can_view",
Object = "project:X",
};
var response = await fgaClient.Check(body, options);
// response.Allowed = false
options = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientCheckRequest(
user="user:anne",
relation="can_view",
object="project:X",
)
response = await fga_client.check(body, options)
# response.allowed = false
fga query check --store-id=$FGA_STORE_ID --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne can_view project:X
# Response: {"allowed":false}
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:anne","relation":"can_view","object":"project:X"}}'
# Response: {"allowed":false}
check(
user = "user:anne", // check if the user `user:anne`
relation = "can_view", // has an `can_view` relation
object = "project:X", // with the object `project:X`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: false
In order for Anne to be authorized, a tuple indicating Anne's current organization context will need to be present:
- Node.js
- Go
- .NET
- Python
- curl
- CLI
- Pseudocode
Initialize the SDK
// Checkout the "How to Setup the SDK Client" page for more details.
const { CredentialsMethod, OpenFgaClient } = require('@openfga/sdk'); // OR import { CredentialsMethod, OpenFgaClient } from '@openfga/sdk';
// Ensure the environment variables are set
// FGA_API_SCHEME = 'https'
// FGA_API_HOST = 'api.us1.fga.dev' for Dev Preview and Early Access / 'api.playground.fga.dev' for the FGA Playground
// FGA_STORE_ID = 'YOUR_STORE_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page
// FGA_MODEL_ID = 'YOUR_MODEL_ID' - optional, can be overridden per request, helps reduce latency
// FGA_API_TOKEN_ISSUER = 'fga.us.auth0.com' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_CLIENT_ID = 'YOUR_CLIENT_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
// FGA_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
const fgaClient = new OpenFgaClient({
apiScheme: process.env.FGA_API_SCHEME,
apiHost: process.env.FGA_API_HOST,
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID,
credentials: { // Credentials are not needed if connecting to the Playground API
method: CredentialsMethod.ClientCredentials,
config: {
apiTokenIssuer: process.env.FGA_API_TOKEN_ISSUER,
apiAudience: process.env.FGA_API_AUDIENCE,
clientId: process.env.FGA_CLIENT_ID,
clientSecret: process.env.FGA_CLIENT_SECRET,
},
},
});
await fgaClient.write({
writes: [
// Anne is authorizing from the context of organization:A
{ user: 'user:anne', relation: 'user_in_context', object: 'organization:A'}]
},
}, {
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
Initialize the SDK
// Checkout the "How to Setup the SDK Client" page for more details.
import (
"os"
openfga "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
"github.com/openfga/go-sdk/credentials"
)
// Ensure the environment variables are set
// FGA_API_SCHEME = 'https'
// FGA_API_HOST = 'api.us1.fga.dev' for Dev Preview and Early Access / 'api.playground.fga.dev' for the FGA Playground
// FGA_STORE_ID = 'YOUR_STORE_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page
// FGA_MODEL_ID = 'YOUR_MODEL_ID' - optional, can be overridden per request, helps reduce latency
// FGA_API_TOKEN_ISSUER = 'fga.us.auth0.com' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_CLIENT_ID = 'YOUR_CLIENT_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
// FGA_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
func main() {
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiScheme: os.Getenv("FGA_API_SCHEME")
ApiHost: os.Getenv("FGA_API_HOST"),
StoreId: os.Getenv("FGA_STORE_ID"),
AuthorizationModelId: openfga.PtrString(os.Getenv("FGA_MODEL_ID")),
Credentials: &credentials.Credentials{ // Credentials are not needed if connecting to the Playground API
Method: credentials.CredentialsMethodClientCredentials,
Config: &credentials.Config{
ClientCredentialsClientId: os.Getenv("FGA_CLIENT_ID"),
ClientCredentialsClientSecret: os.Getenv("FGA_CLIENT_SECRET"),
ClientCredentialsApiAudience: os.Getenv("FGA_API_AUDIENCE"),
ClientCredentialsApiTokenIssuer: os.Getenv("FGA_API_TOKEN_ISSUER"),
},
},
})
if err != nil {
// .. Handle error
}
}
options := ClientWriteOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := fgaClient.ClientWriteRequest{
Writes: &[]ClientTupleKey{
{
// Anne is authorizing from the context of organization:A
User: openfga.PtrString("user:anne"),
Relation: openfga.PtrString("user_in_context"),
Object: openfga.PtrString("organization:A"),
}, } }
data, err := fgaClient.Write(context.Background()).Body(requestBody).Options(options).Execute()
if err != nil {
// .. Handle error
}
Initialize the SDK
// Checkout the "How to Setup the SDK Client" page for more details.
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
// Ensure the environment variables are set
// FGA_API_SCHEME = 'https'
// FGA_API_HOST = 'api.us1.fga.dev' for Dev Preview and Early Access / 'api.playground.fga.dev' for the FGA Playground
// FGA_STORE_ID = 'YOUR_STORE_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page
// FGA_MODEL_ID = 'YOUR_MODEL_ID' - optional, can be overridden per request, helps reduce latency
// FGA_API_TOKEN_ISSUER = 'fga.us.auth0.com' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_CLIENT_ID = 'YOUR_CLIENT_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
// FGA_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
class MyProgram {
static async Task Main() {
var configuration = new ClientConfiguration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"),
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"),
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"),
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"),
Credentials = new Credentials() { // Credentials are not needed if connecting to the Playground API
Method = CredentialsMethod.ClientCredentials,
Config = new CredentialsConfig() {
ApiTokenIssuer = Environment.GetEnvironmentVariable("FGA_API_TOKEN_ISSUER"),
ApiAudience = Environment.GetEnvironmentVariable("FGA_API_AUDIENCE"),
ClientId = Environment.GetEnvironmentVariable("FGA_CLIENT_ID"),
ClientSecret = Environment.GetEnvironmentVariable("FGA_CLIENT_SECRET"),
}
}
};
var fgaClient = new OpenFgaClient(configuration);
}
}
var options = new ClientListObjectsOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientWriteRequest() {
Writes = new List<ClientTupleKey>() {
// Anne is authorizing from the context of organization:A
new() { User = "user:anne", Relation = "user_in_context", Object = "organization:A" }
},
};
var response = await fgaClient.Write(body, options);
Initialize the SDK
# Checkout the "How to Setup the SDK Client" page for more details.
import os
import openfga_sdk
from openfga_sdk.client import OpenFgaClient, ClientConfiguration
from openfga_sdk.credentials import Credentials, CredentialConfiguration
# FGA_API_SCHEME = 'https'
# FGA_API_HOST = 'api.us1.fga.dev' for Dev Preview and Early Access / 'api.playground.fga.dev' for the FGA Playground
# FGA_STORE_ID = 'YOUR_STORE_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page
# FGA_MODEL_ID = 'YOUR_MODEL_ID' - optional, can be overridden per request, helps reduce latency
# FGA_API_TOKEN_ISSUER = 'fga.us.auth0.com' for Dev Preview and Early Access / not needed for the FGA Playground
# FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' for Dev Preview and Early Access / not needed for the FGA Playground
# FGA_CLIENT_ID = 'YOUR_CLIENT_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
# FGA_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
credentials = Credentials(
method='client_credentials',
configuration=CredentialConfiguration(
api_issuer= os.environ.get('FGA_API_TOKEN_ISSUER'),
api_audience= os.environ.get('FGA_API_AUDIENCE'),
client_id= os.environ.get('FGA_CLIENT_ID'),
client_secret= os.environ.get('FGA_CLIENT_SECRET'),
)
)
configuration = ClientConfiguration(
api_scheme = os.environ.get('FGA_API_SCHEME'),
api_host = os.environ.get('FGA_API_HOST'),
store_id = os.environ.get('FGA_STORE_ID'),
model_id = os.environ.get('FGA_MODEL_ID'),
credentials = credentials, # Credentials are not needed if connecting to the Playground API
)
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models() # call requests
await fga_client.close() # close when done
options = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientWriteRequest(
writes=[
ClientTuple(
# Anne is authorizing from the context of organization:A
user="user:anne",
relation="user_in_context",
object="organization:A",
),
],
)
response = await fga_client.write(body, options)
Set the required environment variables
# 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_SERVER_URL='https://api.us1.fga.dev'
# For playground environment
# FGA_SERVER_URL='https://api.playground.fga.dev'
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/write \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"writes": { "tuple_keys" : [{"user":"user:anne","relation":"user_in_context","object":"organization:A"}] }, "authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"}'
Set the required environment variables
# 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_SERVER_URL='https://api.us1.fga.dev'
# For playground environment
# FGA_SERVER_URL='https://api.playground.fga.dev'
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne user_in_context organization:A
write([
// Anne is authorizing from the context of organization:A
{
"user":"user:anne",
"relation":"user_in_context",
"object":"organization:A"
}
], authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw")
We can verify this by running a check request
- Node.js
- Go
- .NET
- Python
- CLI
- curl
- Pseudocode
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:anne',
relation: 'can_view',
object: 'project:X',
}, {
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
});
// allowed = true
options := ClientCheckOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := ClientCheckRequest{
User: "user:anne",
Relation: "can_view",
Object: "project:X",
}
data, err := fgaClient.Check(context.Background()).Body(body).Options(options).Execute()
// data = { allowed: true }
var options = new ClientCheckOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientCheckRequest {
User = "user:anne",
Relation = "can_view",
Object = "project:X",
};
var response = await fgaClient.Check(body, options);
// response.Allowed = true
options = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientCheckRequest(
user="user:anne",
relation="can_view",
object="project:X",
)
response = await fga_client.check(body, options)
# response.allowed = true
fga query check --store-id=$FGA_STORE_ID --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne can_view project:X
# Response: {"allowed":true}
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:anne","relation":"can_view","object":"project:X"}}'
# Response: {"allowed":true}
check(
user = "user:anne", // check if the user `user:anne`
relation = "can_view", // has an `can_view` relation
object = "project:X", // with the object `project:X`
authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: true
Use Contextual Tuples For Context Related Checks
Now that we know we can authorize based on present state, we have a different problem to solve. We are storing the tuples in the state in order for Okta FGA to evaluate them, which fails in certain use-cases where Anne can be connected to two different contexts in different browser windows at the same time, as each has a different context at the same time, so if they are written to the state, which will Okta FGA use to compute Anne's access to the project?
For Check calls, Okta FGA has a concept called "Contextual Tuples". Contextual Tuples are tuples that do not exist in the system state and are not written beforehand to Okta FGA. They are tuples that are sent alongside the Check request and will be treated as if they already exist in the state for the context of that particular Check call. That means that Anne can be using two different sessions, each within a different organization context, and Okta FGA will correctly respond to each one with the correct authorization decision.
First, we will undo the temporary step and remove the stored tuples for which Anne has a user_in_context
relation with organization:A
.
- Node.js
- Go
- .NET
- Python
- curl
- CLI
- Pseudocode
Initialize the SDK
// Checkout the "How to Setup the SDK Client" page for more details.
const { CredentialsMethod, OpenFgaClient } = require('@openfga/sdk'); // OR import { CredentialsMethod, OpenFgaClient } from '@openfga/sdk';
// Ensure the environment variables are set
// FGA_API_SCHEME = 'https'
// FGA_API_HOST = 'api.us1.fga.dev' for Dev Preview and Early Access / 'api.playground.fga.dev' for the FGA Playground
// FGA_STORE_ID = 'YOUR_STORE_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page
// FGA_MODEL_ID = 'YOUR_MODEL_ID' - optional, can be overridden per request, helps reduce latency
// FGA_API_TOKEN_ISSUER = 'fga.us.auth0.com' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_CLIENT_ID = 'YOUR_CLIENT_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
// FGA_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
const fgaClient = new OpenFgaClient({
apiScheme: process.env.FGA_API_SCHEME,
apiHost: process.env.FGA_API_HOST,
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID,
credentials: { // Credentials are not needed if connecting to the Playground API
method: CredentialsMethod.ClientCredentials,
config: {
apiTokenIssuer: process.env.FGA_API_TOKEN_ISSUER,
apiAudience: process.env.FGA_API_AUDIENCE,
clientId: process.env.FGA_CLIENT_ID,
clientSecret: process.env.FGA_CLIENT_SECRET,
},
},
});
await fgaClient.write({
deletes: [
// Delete stored tuples where Anne is authorizing from the context of organization:A
{ user: 'user:anne', relation: 'user_in_context', object: 'organization:A'}]
},
}, {
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
Initialize the SDK
// Checkout the "How to Setup the SDK Client" page for more details.
import (
"os"
openfga "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
"github.com/openfga/go-sdk/credentials"
)
// Ensure the environment variables are set
// FGA_API_SCHEME = 'https'
// FGA_API_HOST = 'api.us1.fga.dev' for Dev Preview and Early Access / 'api.playground.fga.dev' for the FGA Playground
// FGA_STORE_ID = 'YOUR_STORE_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page
// FGA_MODEL_ID = 'YOUR_MODEL_ID' - optional, can be overridden per request, helps reduce latency
// FGA_API_TOKEN_ISSUER = 'fga.us.auth0.com' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_CLIENT_ID = 'YOUR_CLIENT_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
// FGA_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
func main() {
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiScheme: os.Getenv("FGA_API_SCHEME")
ApiHost: os.Getenv("FGA_API_HOST"),
StoreId: os.Getenv("FGA_STORE_ID"),
AuthorizationModelId: openfga.PtrString(os.Getenv("FGA_MODEL_ID")),
Credentials: &credentials.Credentials{ // Credentials are not needed if connecting to the Playground API
Method: credentials.CredentialsMethodClientCredentials,
Config: &credentials.Config{
ClientCredentialsClientId: os.Getenv("FGA_CLIENT_ID"),
ClientCredentialsClientSecret: os.Getenv("FGA_CLIENT_SECRET"),
ClientCredentialsApiAudience: os.Getenv("FGA_API_AUDIENCE"),
ClientCredentialsApiTokenIssuer: os.Getenv("FGA_API_TOKEN_ISSUER"),
},
},
})
if err != nil {
// .. Handle error
}
}
options := ClientWriteOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := fgaClient.ClientWriteRequest{
Deletes: &[]ClientTupleKey{
{
// Delete stored tuples where Anne is authorizing from the context of organization:A
User: openfga.PtrString("user:anne"),
Relation: openfga.PtrString("user_in_context"),
Object: openfga.PtrString("organization:A"),
}, } }
data, err := fgaClient.Write(context.Background()).Body(requestBody).Options(options).Execute()
if err != nil {
// .. Handle error
}
Initialize the SDK
// Checkout the "How to Setup the SDK Client" page for more details.
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
// Ensure the environment variables are set
// FGA_API_SCHEME = 'https'
// FGA_API_HOST = 'api.us1.fga.dev' for Dev Preview and Early Access / 'api.playground.fga.dev' for the FGA Playground
// FGA_STORE_ID = 'YOUR_STORE_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page
// FGA_MODEL_ID = 'YOUR_MODEL_ID' - optional, can be overridden per request, helps reduce latency
// FGA_API_TOKEN_ISSUER = 'fga.us.auth0.com' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_CLIENT_ID = 'YOUR_CLIENT_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
// FGA_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
class MyProgram {
static async Task Main() {
var configuration = new ClientConfiguration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"),
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"),
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"),
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"),
Credentials = new Credentials() { // Credentials are not needed if connecting to the Playground API
Method = CredentialsMethod.ClientCredentials,
Config = new CredentialsConfig() {
ApiTokenIssuer = Environment.GetEnvironmentVariable("FGA_API_TOKEN_ISSUER"),
ApiAudience = Environment.GetEnvironmentVariable("FGA_API_AUDIENCE"),
ClientId = Environment.GetEnvironmentVariable("FGA_CLIENT_ID"),
ClientSecret = Environment.GetEnvironmentVariable("FGA_CLIENT_SECRET"),
}
}
};
var fgaClient = new OpenFgaClient(configuration);
}
}
var options = new ClientListObjectsOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientWriteRequest() {
Deletes = new List<ClientTupleKey>() {
// Delete stored tuples where Anne is authorizing from the context of organization:A
new() { User = "user:anne", Relation = "user_in_context", Object = "organization:A" }
},
};
var response = await fgaClient.Write(body, options);
Initialize the SDK
# Checkout the "How to Setup the SDK Client" page for more details.
import os
import openfga_sdk
from openfga_sdk.client import OpenFgaClient, ClientConfiguration
from openfga_sdk.credentials import Credentials, CredentialConfiguration
# FGA_API_SCHEME = 'https'
# FGA_API_HOST = 'api.us1.fga.dev' for Dev Preview and Early Access / 'api.playground.fga.dev' for the FGA Playground
# FGA_STORE_ID = 'YOUR_STORE_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page
# FGA_MODEL_ID = 'YOUR_MODEL_ID' - optional, can be overridden per request, helps reduce latency
# FGA_API_TOKEN_ISSUER = 'fga.us.auth0.com' for Dev Preview and Early Access / not needed for the FGA Playground
# FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' for Dev Preview and Early Access / not needed for the FGA Playground
# FGA_CLIENT_ID = 'YOUR_CLIENT_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
# FGA_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
credentials = Credentials(
method='client_credentials',
configuration=CredentialConfiguration(
api_issuer= os.environ.get('FGA_API_TOKEN_ISSUER'),
api_audience= os.environ.get('FGA_API_AUDIENCE'),
client_id= os.environ.get('FGA_CLIENT_ID'),
client_secret= os.environ.get('FGA_CLIENT_SECRET'),
)
)
configuration = ClientConfiguration(
api_scheme = os.environ.get('FGA_API_SCHEME'),
api_host = os.environ.get('FGA_API_HOST'),
store_id = os.environ.get('FGA_STORE_ID'),
model_id = os.environ.get('FGA_MODEL_ID'),
credentials = credentials, # Credentials are not needed if connecting to the Playground API
)
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models() # call requests
await fga_client.close() # close when done
options = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientWriteRequest(
deletes=[
ClientTuple(
# Delete stored tuples where Anne is authorizing from the context of organization:A
user="user:anne",
relation="user_in_context",
object="organization:A",
),
],
)
response = await fga_client.write(body, options)
Set the required environment variables
# 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_SERVER_URL='https://api.us1.fga.dev'
# For playground environment
# FGA_SERVER_URL='https://api.playground.fga.dev'
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/write \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"deletes": { "tuple_keys" : [{"user":"user:anne","relation":"user_in_context","object":"organization:A"}] }, "authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"}'
Set the required environment variables
# 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_SERVER_URL='https://api.us1.fga.dev'
# For playground environment
# FGA_SERVER_URL='https://api.playground.fga.dev'
fga tuple delete --store-id=${FGA_STORE_ID} user:anne user_in_context organization:A
delete([
// Delete stored tuples where Anne is authorizing from the context of organization:A
{
"user":"user:anne",
"relation":"user_in_context",
"object":"organization:A"
}
], authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw")
Next, when Anne is connecting from the context of organization A, Okta FGA will return {"allowed":true}
:
- Node.js
- Go
- .NET
- Python
- CLI
- curl
- Pseudocode
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:anne',
relation: 'can_view',
object: 'project:X',
contextual_tuples: [
{
user: "user:anne",
relation: "user_in_context",
object: "organization:A"
}
]}, {
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
});
// allowed = true
options := ClientCheckOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := ClientCheckRequest{
User: "user:anne",
Relation: "can_view",
Object: "project:X",
ContextualTuples: &[]ClientTupleKey{
{
User: "user:anne",
Relation: "user_in_context",
Object: "organization:A",
}
}
}
data, err := fgaClient.Check(context.Background()).Body(body).Options(options).Execute()
// data = { allowed: true }
var options = new ClientCheckOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientCheckRequest {
User = "user:anne",
Relation = "can_view",
Object = "project:X",,
ContextualTuples = new List<ClientTupleKey>({
new(user: "user:anne", relation: "user_in_context", _object: "organization:A")
})
};
var response = await fgaClient.Check(body, options);
// response.Allowed = true
options = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientCheckRequest(
user="user:anne",
relation="can_view",
object="project:X",
contextual_tuples=[
ClientTupleKey(user="user:anne", relation="user_in_context", object="organization:A")
],
)
response = await fga_client.check(body, options)
# response.allowed = true
fga query check --store-id=$FGA_STORE_ID --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne can_view project:X --contextual_tuples "user:anne user_in_context organization:A"
# Response: {"allowed":true}
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:anne","relation":"can_view","object":"project:X"},"contextual_tuples":{"tuple_keys":[{"user":"user:anne","relation":"user_in_context","object":"organization:A"}]}}'
# Response: {"allowed":true}
check(
user = "user:anne", // check if the user `user:anne`
relation = "can_view", // has an `can_view` relation
object = "project:X", // with the object `project:X`
contextual_tuples = [ // Assuming the following is true
{user = "user:anne", relation = "user_in_context", object = "organization:A"}
], authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: true
When Anne is connecting from the context of organization C, Okta FGA will return {"allowed":false}
:
- Node.js
- Go
- .NET
- Python
- CLI
- curl
- Pseudocode
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:anne',
relation: 'can_view',
object: 'project:X',
contextual_tuples: [
{
user: "user:anne",
relation: "user_in_context",
object: "organization:C"
}
]}, {
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
});
// allowed = false
options := ClientCheckOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := ClientCheckRequest{
User: "user:anne",
Relation: "can_view",
Object: "project:X",
ContextualTuples: &[]ClientTupleKey{
{
User: "user:anne",
Relation: "user_in_context",
Object: "organization:C",
}
}
}
data, err := fgaClient.Check(context.Background()).Body(body).Options(options).Execute()
// data = { allowed: false }
var options = new ClientCheckOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientCheckRequest {
User = "user:anne",
Relation = "can_view",
Object = "project:X",,
ContextualTuples = new List<ClientTupleKey>({
new(user: "user:anne", relation: "user_in_context", _object: "organization:C")
})
};
var response = await fgaClient.Check(body, options);
// response.Allowed = false
options = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientCheckRequest(
user="user:anne",
relation="can_view",
object="project:X",
contextual_tuples=[
ClientTupleKey(user="user:anne", relation="user_in_context", object="organization:C")
],
)
response = await fga_client.check(body, options)
# response.allowed = false
fga query check --store-id=$FGA_STORE_ID --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne can_view project:X --contextual_tuples "user:anne user_in_context organization:C"
# Response: {"allowed":false}
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/check \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw", "tuple_key":{"user":"user:anne","relation":"can_view","object":"project:X"},"contextual_tuples":{"tuple_keys":[{"user":"user:anne","relation":"user_in_context","object":"organization:C"}]}}'
# Response: {"allowed":false}
check(
user = "user:anne", // check if the user `user:anne`
relation = "can_view", // has an `can_view` relation
object = "project:X", // with the object `project:X`
contextual_tuples = [ // Assuming the following is true
{user = "user:anne", relation = "user_in_context", object = "organization:C"}
], authorization_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
);
Reply: false
Using this, you can check that the following requirements are satisfied:
User | Organization Context | Action | Allowed |
---|---|---|---|
Anne | Organization A | View | Yes |
Anne | Organization B | View | Yes |
Anne | Organization C | View | Yes |
Anne | Organization A | Delete | Yes |
Anne | Organization B | Delete | No |
Anne | Organization C | Delete | No |
Beth | Organization B | View | Yes |
Beth | Organization B | Delete | No |
Carl | Organization C | View | No |
Carl | Organization C | Delete | No |
Summary
Final version of the Authorization Model and Relationship tuples
- DSL
- JSON
model
schema 1.1
type user
type organization
relations
define member: [user]
define project_manager: [user] and user_in_context
define base_project_editor: [user] or project_manager
define project_editor: base_project_editor and user_in_context
define user_in_context: [user]
type project
relations
define owner: [organization]
define partner: [organization]
define manager: project_manager from owner
define editor: manager or project_editor from owner or project_editor from partner
define can_delete: manager
define can_edit: editor
define can_view: editor
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "organization",
"relations": {
"member": {
"this": {}
},
"project_manager": {
"intersection": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"object": "",
"relation": "user_in_context"
}
}
]
}
},
"base_project_editor": {
"union": {
"child": [
{
"this": {}
},
{
"computedUserset": {
"object": "",
"relation": "project_manager"
}
}
]
}
},
"project_editor": {
"intersection": {
"child": [
{
"computedUserset": {
"object": "",
"relation": "base_project_editor"
}
},
{
"computedUserset": {
"object": "",
"relation": "user_in_context"
}
}
]
}
},
"user_in_context": {
"this": {}
}
},
"metadata": {
"relations": {
"member": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"project_manager": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"base_project_editor": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"user_in_context": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
},
{
"type": "project",
"relations": {
"owner": {
"this": {}
},
"partner": {
"this": {}
},
"manager": {
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "owner"
},
"computedUserset": {
"object": "",
"relation": "project_manager"
}
}
},
"editor": {
"union": {
"child": [
{
"computedUserset": {
"object": "",
"relation": "manager"
}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "owner"
},
"computedUserset": {
"object": "",
"relation": "project_editor"
}
}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "partner"
},
"computedUserset": {
"object": "",
"relation": "project_editor"
}
}
}
]
}
},
"can_delete": {
"computedUserset": {
"object": "",
"relation": "manager"
}
},
"can_edit": {
"computedUserset": {
"object": "",
"relation": "editor"
}
},
"can_view": {
"computedUserset": {
"object": "",
"relation": "editor"
}
}
},
"metadata": {
"relations": {
"owner": {
"directly_related_user_types": [
{
"type": "organization"
}
]
},
"partner": {
"directly_related_user_types": [
{
"type": "organization"
}
]
}
}
}
}
]
}
- Node.js
- Go
- .NET
- Python
- curl
- CLI
- Pseudocode
await fgaClient.write({
writes: [
// Anne has a `project manager` role at organization A
{ user: 'user:anne', relation: 'project_manager', object: 'organization:A'},
// Anne has a `project manager` role at organization B
{ user: 'user:anne', relation: 'project_manager', object: 'organization:B'},
// Anne has a `project manager` role at organization C
{ user: 'user:anne', relation: 'project_manager', object: 'organization:C'},
// Beth has a `project manager` role at organization B
{ user: 'user:beth', relation: 'project_manager', object: 'organization:B'},
// Carl has a `project manager` role at organization C
{ user: 'user:carl', relation: 'project_manager', object: 'organization:C'},
// Organization A owns Project X
{ user: 'organization:A', relation: 'owner', object: 'project:X'},
// Project X is shared with Organization B
{ user: 'organization:B', relation: 'partner', object: 'project:X'}]
},
}, {
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
options := ClientWriteOptions{
AuthorizationModelId: openfga.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw"),
}
body := fgaClient.ClientWriteRequest{
Writes: &[]ClientTupleKey{
{
// Anne has a `project manager` role at organization A
User: openfga.PtrString("user:anne"),
Relation: openfga.PtrString("project_manager"),
Object: openfga.PtrString("organization:A"),
},
{
// Anne has a `project manager` role at organization B
User: openfga.PtrString("user:anne"),
Relation: openfga.PtrString("project_manager"),
Object: openfga.PtrString("organization:B"),
},
{
// Anne has a `project manager` role at organization C
User: openfga.PtrString("user:anne"),
Relation: openfga.PtrString("project_manager"),
Object: openfga.PtrString("organization:C"),
},
{
// Beth has a `project manager` role at organization B
User: openfga.PtrString("user:beth"),
Relation: openfga.PtrString("project_manager"),
Object: openfga.PtrString("organization:B"),
},
{
// Carl has a `project manager` role at organization C
User: openfga.PtrString("user:carl"),
Relation: openfga.PtrString("project_manager"),
Object: openfga.PtrString("organization:C"),
},
{
// Organization A owns Project X
User: openfga.PtrString("organization:A"),
Relation: openfga.PtrString("owner"),
Object: openfga.PtrString("project:X"),
},
{
// Project X is shared with Organization B
User: openfga.PtrString("organization:B"),
Relation: openfga.PtrString("partner"),
Object: openfga.PtrString("project:X"),
}, } }
data, err := fgaClient.Write(context.Background()).Body(requestBody).Options(options).Execute()
if err != nil {
// .. Handle error
}
var options = new ClientListObjectsOptions {
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw",
};
var body = new ClientWriteRequest() {
Writes = new List<ClientTupleKey>() {
// Anne has a `project manager` role at organization A
new() { User = "user:anne", Relation = "project_manager", Object = "organization:A" },
// Anne has a `project manager` role at organization B
new() { User = "user:anne", Relation = "project_manager", Object = "organization:B" },
// Anne has a `project manager` role at organization C
new() { User = "user:anne", Relation = "project_manager", Object = "organization:C" },
// Beth has a `project manager` role at organization B
new() { User = "user:beth", Relation = "project_manager", Object = "organization:B" },
// Carl has a `project manager` role at organization C
new() { User = "user:carl", Relation = "project_manager", Object = "organization:C" },
// Organization A owns Project X
new() { User = "organization:A", Relation = "owner", Object = "project:X" },
// Project X is shared with Organization B
new() { User = "organization:B", Relation = "partner", Object = "project:X" }
},
};
var response = await fgaClient.Write(body, options);
options = {
"authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
}
body = ClientWriteRequest(
writes=[
ClientTuple(
# Anne has a `project manager` role at organization A
user="user:anne",
relation="project_manager",
object="organization:A",
),
ClientTuple(
# Anne has a `project manager` role at organization B
user="user:anne",
relation="project_manager",
object="organization:B",
),
ClientTuple(
# Anne has a `project manager` role at organization C
user="user:anne",
relation="project_manager",
object="organization:C",
),
ClientTuple(
# Beth has a `project manager` role at organization B
user="user:beth",
relation="project_manager",
object="organization:B",
),
ClientTuple(
# Carl has a `project manager` role at organization C
user="user:carl",
relation="project_manager",
object="organization:C",
),
ClientTuple(
# Organization A owns Project X
user="organization:A",
relation="owner",
object="project:X",
),
ClientTuple(
# Project X is shared with Organization B
user="organization:B",
relation="partner",
object="project:X",
),
],
)
response = await fga_client.write(body, options)
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/write \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"writes": { "tuple_keys" : [{"user":"user:anne","relation":"project_manager","object":"organization:A"},{"user":"user:anne","relation":"project_manager","object":"organization:B"},{"user":"user:anne","relation":"project_manager","object":"organization:C"},{"user":"user:beth","relation":"project_manager","object":"organization:B"},{"user":"user:carl","relation":"project_manager","object":"organization:C"},{"user":"organization:A","relation":"owner","object":"project:X"},{"user":"organization:B","relation":"partner","object":"project:X"}] }, "authorization_model_id": "1uHxCSuTP0VKPYSnkq1pbb1jeZw"}'
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne project_manager organization:A
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne project_manager organization:B
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:anne project_manager organization:C
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:beth project_manager organization:B
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw user:carl project_manager organization:C
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw organization:A owner project:X
fga tuple write --store-id=${FGA_STORE_ID} --model-id=1uHxCSuTP0VKPYSnkq1pbb1jeZw organization:B partner project:X
write([
// Anne has a `project manager` role at organization A
{
"user":"user:anne",
"relation":"project_manager",
"object":"organization:A"
},
// Anne has a `project manager` role at organization B
{
"user":"user:anne",
"relation":"project_manager",
"object":"organization:B"
},
// Anne has a `project manager` role at organization C
{
"user":"user:anne",
"relation":"project_manager",
"object":"organization:C"
},
// Beth has a `project manager` role at organization B
{
"user":"user:beth",
"relation":"project_manager",
"object":"organization:B"
},
// Carl has a `project manager` role at organization C
{
"user":"user:carl",
"relation":"project_manager",
"object":"organization:C"
},
// Organization A owns Project X
{
"user":"organization:A",
"relation":"owner",
"object":"project:X"
},
// Project X is shared with Organization B
{
"user":"organization:B",
"relation":"partner",
"object":"project:X"
}
], authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw")
Contextual tuples:
- Are not persisted in the store.
- Are only supported on the Check API endpoint and ListObjects API endpoint. They are not supported on read, expand and other endpoints.
- If you are using the Read Changes API endpoint to build a permission aware search index, note that it will not be trivial to take contextual tuples into account.
Related Sections
Learn how to model requiring multiple relationships before users are authorized to perform certain actions.
Learn how to authorize access that depends on dynamic or contextual criteria.