Parent-Child Objects
In OpenFGA, a user's relationship with an object can affect their relationship with another object. For example, an editor
of a folder
can also be an editor
of all documents
that folder
is a parent
of.
Object-to-object relationships can combine with a configured authorization model to indicate that a user's relationship with one object may influence the user's relationship with another object. They can also eliminate the need to modify relationships between objects using user groups.
The follow are examples of simple object-to-object relationships:
managers
of anemployee
have access toapprove
requests theemployee
has made- users who have a repository admin role (
repo_admin
) in an organization automatically haveadmin
access to all repositories in that organization - users who are
subscribed
to aplan
get access to all thefeatures
in thatplan
Before you start
Familiarize yourself with basic OpenFGA Concepts:
Assume that you have the following authorization model.
You have two types:
folder
that users can be related to as an editor
document
that users can be related to as an editor
You have two types:
folder
that users can be related to as an editor
document
that users can be related to as an editor
- DSL
- JSON
model
schema 1.1
type user
type folder
relations
define editor: [user]
type document
relations
define editor: [user]
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "folder",
"relations": {
"editor": {
"this": {}
}
},
"metadata": {
"relations": {
"editor": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
},
{
"type": "document",
"relations": {
"editor": {
"this": {}
}
},
"metadata": {
"relations": {
"editor": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
}
]
}
In addition:
Direct access
Creating an authorization model and a relationship tuple can grant a user access to an object. To learn more, read about Direct Access
OpenFGA concepts
- A Type: a class of objects that have similar characteristics
- A User: an entity in the system that can be related to an object
- A Relation: 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
- An Object: represents an entity in the system. Users' relationships to it can be define through relationship tuples and the authorization model
- A Relationship Tuple: a group stored in OpenFGA that consists of a user, a relation, and an object
- Union Operator: can be used to indicate that the user has multiple ways of being related to an object
Step by step
The following walkthrough models (a) folders that contain documents and (b) that a user who has editor access to a given folder has editor access to all documents in that folder.
For editors
of a folder
to be editors
of a containing document
, you must:
- Update the authorization model to allow a
parent
relationship betweenfolder
anddocument
- Update the
editor
relation in thedocument
type definition to support cascading fromfolder
The following three steps indicate and verify that bob
is an editor
of document:meeting_notes.doc
because bob
is an editor
of folder:notes
:
- Create a new relationship tuple to indicate that bob is a
editor
of folder:notes - Create a new relationship tuple to indicate that folder:notes is a
parent
of document:meeting_notes.doc - Check to see if bob is an
editor
of document:meeting_notes.doc
01. Update the Athorization Model to allow a parent relationship between folder and document
As documented in Modeling Concepts: Object to Object Relationships, the following update to the authorization model allows a parent
relation between a folder
and a document
:
- DSL
- JSON
model
schema 1.1
type user
type folder
relations
define editor: [user]
type document
relations
define parent: [folder]
define editor: [user]
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "folder",
"relations": {
"editor": {
"this": {}
}
},
"metadata": {
"relations": {
"editor": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
},
{
"type": "document",
"relations": {
"parent": {
"this": {}
},
"editor": {
"this": {}
}
},
"metadata": {
"relations": {
"parent": {
"directly_related_user_types": [
{
"type": "folder"
}
]
},
"editor": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
}
]
}
The document
type now has a parent
relation, indicating that other objects can be parent
s of document
s
02. Update the editor relation in the document type definition to support cascading from folder
To allow cascading relations between folder
and document
, update the authorization model:
- DSL
- JSON
model
schema 1.1
type user
type folder
relations
define editor: [user]
type document
relations
define parent: [folder]
define editor: [user] or editor from parent
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "folder",
"relations": {
"editor": {
"this": {}
}
},
"metadata": {
"relations": {
"editor": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
},
{
"type": "document",
"relations": {
"parent": {
"this": {}
},
"editor": {
"union": {
"child": [
{
"this": {}
},
{
"tupleToUserset": {
"tupleset": {
"relation": "parent"
},
"computedUserset": {
"relation": "editor"
}
}
}
]
}
}
},
"metadata": {
"relations": {
"parent": {
"directly_related_user_types": [
{
"type": "folder"
}
]
},
"editor": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
}
]
}
editor
of a document
can be the following:
- users that are directly assigned as editors
- users that are related to any
parent
of this document aseditor
(editors of the parent)
After making these changes, anyone related to a folder
that is a parent
of a document
as an editor
is also an editor
of that document
.
03. Create a new relationship tuple to indicate that bob
is an editor
of folder:notes
To leverage the new cascading relation, create a relationship tuple stating that bob
is an editor
of folder:notes
- Node.js
- Go
- .NET
- Python
- Java
- curl
- CLI
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});
await fgaClient.write({
writes: [
{"user":"user:bob","relation":"editor","object":"folder:notes"}
],
}, {
authorization_model_id: "01HVMMBCMGZNT3SED4Z17ECXCA"
});
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
"os"
. "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Optional, can be overridden per request
})
if err != nil {
// .. Handle error
}
}
options := ClientWriteOptions{
AuthorizationModelId: PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
}
body := ClientWriteRequest{
Writes: []ClientTupleKey{
{
User: "user:bob",
Relation: "editor",
Object: "folder:notes",
},
},
}
data, err := fgaClient.Write(context.Background()).
Body(body).
Options(options).
Execute()
if err != nil {
// .. Handle error
}
_ = data // use the response
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
class Example {
public static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"), ?? "http://localhost:8080", // required, e.g. https://api.fga.example
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Optional, can be overridden per request
};
var fgaClient = new OpenFgaClient(configuration);
}
}
var options = new ClientWriteOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA",
};
var body = new ClientWriteRequest() {
Writes = new List<ClientTupleKey>() {
new() {
User = "user:bob",
Relation = "editor",
Object = "folder:notes"
}
},
};
var response = await fgaClient.Write(body, options);
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import asyncio
import os
import json
from openfga_sdk.client import ClientConfiguration, OpenFgaClient
async def main():
configuration = ClientConfiguration(
api_url = os.environ.get('FGA_API_URL'), # required, e.g. https://api.fga.example
store_id = os.environ.get('FGA_STORE_ID'), # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
authorization_model_id = os.environ.get('FGA_MODEL_ID'), # Optional, can be overridden per request
)
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
asyncio.run(main())
options = {
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA"
}
body = ClientWriteRequest(
writes=[
ClientTuple(
user="user:bob",
relation="editor",
object="folder:notes",
),
],
)
response = await fga_client.write(body, options)
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request
var fgaClient = new OpenFgaClient(config);
}
}
var options = new ClientWriteOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA");
var body = new ClientWriteRequest()
.writes(List.of(
new ClientTupleKey()
.user("user:bob")
.relation("editor")
._object("folder:notes")
));
var response = fgaClient.write(body, options).get();
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
curl -X POST $FGA_API_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:bob","relation":"editor","object":"folder:notes"}] }, "authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA"}'
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
fga tuple write --store-id=${FGA_STORE_ID} --model-id=01HVMMBCMGZNT3SED4Z17ECXCA user:bob editor folder:notes
write([
{
"user":"user:bob",
"relation":"editor",
"object":"folder:notes"
}
], authorization_model_id="01HVMMBCMGZNT3SED4Z17ECXCA")
Note: Use unique ids for each object and user within your application domain when creating relationship tuples for OpenFGA. We use first names and simple ids below as an easy-to-follow example.
04. Create a new relationship tuple to indicate that folder:notes
is a parent
of document:meeting_notes.doc
Now that bob
is an editor
of folder:notes
, we need to indicate that folder:notes is a parent
of document:meeting_notes.doc
- Node.js
- Go
- .NET
- Python
- Java
- curl
- CLI
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});
await fgaClient.write({
writes: [
// the notes folder is a parent of the meeting notes document
{"_description":"the notes folder is a parent of the meeting notes document","user":"folder:notes","relation":"parent","object":"document:meeting_notes.doc"}
],
}, {
authorization_model_id: "01HVMMBCMGZNT3SED4Z17ECXCA"
});
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
"os"
. "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Optional, can be overridden per request
})
if err != nil {
// .. Handle error
}
}
options := ClientWriteOptions{
AuthorizationModelId: PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
}
body := ClientWriteRequest{
Writes: []ClientTupleKey{
{
// the notes folder is a parent of the meeting notes document
User: "folder:notes",
Relation: "parent",
Object: "document:meeting_notes.doc",
},
},
}
data, err := fgaClient.Write(context.Background()).
Body(body).
Options(options).
Execute()
if err != nil {
// .. Handle error
}
_ = data // use the response
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
class Example {
public static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"), ?? "http://localhost:8080", // required, e.g. https://api.fga.example
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Optional, can be overridden per request
};
var fgaClient = new OpenFgaClient(configuration);
}
}
var options = new ClientWriteOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA",
};
var body = new ClientWriteRequest() {
Writes = new List<ClientTupleKey>() {
// the notes folder is a parent of the meeting notes document
new() {
User = "folder:notes",
Relation = "parent",
Object = "document:meeting_notes.doc"
}
},
};
var response = await fgaClient.Write(body, options);
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import asyncio
import os
import json
from openfga_sdk.client import ClientConfiguration, OpenFgaClient
async def main():
configuration = ClientConfiguration(
api_url = os.environ.get('FGA_API_URL'), # required, e.g. https://api.fga.example
store_id = os.environ.get('FGA_STORE_ID'), # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
authorization_model_id = os.environ.get('FGA_MODEL_ID'), # Optional, can be overridden per request
)
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
asyncio.run(main())
options = {
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA"
}
body = ClientWriteRequest(
writes=[
ClientTuple(
# the notes folder is a parent of the meeting notes document
user="folder:notes",
relation="parent",
object="document:meeting_notes.doc",
),
],
)
response = await fga_client.write(body, options)
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request
var fgaClient = new OpenFgaClient(config);
}
}
var options = new ClientWriteOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA");
var body = new ClientWriteRequest()
.writes(List.of(
// the notes folder is a parent of the meeting notes document
new ClientTupleKey()
.user("folder:notes")
.relation("parent")
._object("document:meeting_notes.doc")
));
var response = fgaClient.write(body, options).get();
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
curl -X POST $FGA_API_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" : [{"_description":"the notes folder is a parent of the meeting notes document","user":"folder:notes","relation":"parent","object":"document:meeting_notes.doc"}] }, "authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA"}'
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
fga tuple write --store-id=${FGA_STORE_ID} --model-id=01HVMMBCMGZNT3SED4Z17ECXCA folder:notes parent document:meeting_notes.doc
write([
// the notes folder is a parent of the meeting notes document
{
"user":"folder:notes",
"relation":"parent",
"object":"document:meeting_notes.doc"
}
], authorization_model_id="01HVMMBCMGZNT3SED4Z17ECXCA")
05. Check if bob
is an editor
of document:meeting_notes.doc
After changing the authorization model and adding two new relationship tuples, verify that your configuration is correct by running the following check: is bob an editor of document:meeting_notes.doc.
- Node.js
- Go
- .NET
- Python
- Java
- CLI
- curl
- Pseudocode
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});
// Run a check
const { allowed } = await fgaClient.check({
user: 'user:bob',
relation: 'editor',
object: 'document:meeting_notes.doc',
}, {
authorization_model_id: '01HVMMBCMGZNT3SED4Z17ECXCA',
});
// allowed = true
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import (
"os"
. "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Optional, can be overridden per request
})
if err != nil {
// .. Handle error
}
}
options := ClientCheckOptions{
AuthorizationModelId: PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
}
body := ClientCheckRequest{
User: "user:bob",
Relation: "editor",
Object: "document:meeting_notes.doc",
}
data, err := fgaClient.Check(context.Background()).
Body(body).
Options(options).
Execute()
// data = { allowed: true }
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
class Example {
public static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"), ?? "http://localhost:8080", // required, e.g. https://api.fga.example
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Optional, can be overridden per request
};
var fgaClient = new OpenFgaClient(configuration);
}
}
var options = new ClientCheckOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA",
};
var body = new ClientCheckRequest {
User = "user:bob",
Relation = "editor",
Object = "document:meeting_notes.doc",
};
var response = await fgaClient.Check(body, options);
// response.Allowed = true
Initialize the SDK
# ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import asyncio
import os
import json
from openfga_sdk.client import ClientConfiguration, OpenFgaClient
async def main():
configuration = ClientConfiguration(
api_url = os.environ.get('FGA_API_URL'), # required, e.g. https://api.fga.example
store_id = os.environ.get('FGA_STORE_ID'), # optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
authorization_model_id = os.environ.get('FGA_MODEL_ID'), # Optional, can be overridden per request
)
# Enter a context with an instance of the OpenFgaClient
async with OpenFgaClient(configuration) as fga_client:
api_response = await fga_client.read_authorization_models()
await fga_client.close()
asyncio.run(main())
options = {
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA"
}
body = ClientCheckRequest(
user="user:bob",
relation="editor",
object="document:meeting_notes.doc",
)
response = await fga_client.check(body, options)
# response.allowed = true
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request
var fgaClient = new OpenFgaClient(config);
}
}
var options = new ClientCheckOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA");
var body = new ClientCheckRequest()
.user("user:bob")
.relation("editor")
._object("document:meeting_notes.doc");
var response = fgaClient.check(body, options).get();
// response.getAllowed() = true
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
fga query check --store-id=$FGA_STORE_ID --model-id=01HVMMBCMGZNT3SED4Z17ECXCA user:bob editor document:meeting_notes.doc
# Response: {"allowed":true}
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
curl -X POST $FGA_API_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": "01HVMMBCMGZNT3SED4Z17ECXCA", "tuple_key":{"user":"user:bob","relation":"editor","object":"document:meeting_notes.doc"}}'
# Response: {"allowed":true}
check(
user = "user:bob", // check if the user `user:bob`
relation = "editor", // has an `editor` relation
object = "document:meeting_notes.doc", // with the object `document:meeting_notes.doc`
authorization_id = "01HVMMBCMGZNT3SED4Z17ECXCA"
);
Reply: true
Note: There are no other relationship tuples in the store that dictate a direct relation between
bob
anddocument:meeting_notes.doc
. The check succeeds because of the cascading relation.
The chain of resolution is:
bob
is aneditor
offolder:notes
folder:notes
is aparent
ofdocument:meeting_notes.doc
editors
of anyparent
folder
ofdocument:meeting_notes.doc
are alsoeditors
of thedocument
- therefore
bob
is aneditor
ofdocument:meeting_notes.doc
When searching tuples that are related to the object (the word after from
, also called the tupleset), OpenFGA will not do any evaluation and only considers concrete objects (of the form <object_type>:<object_id>
) that were directly assigned. OpenFGA will throw an error if it encounters any rewrites, a *
, a type bound public access (<object_type>:*
), or a userset (<object_type>:<object_id>#<relation>
).
For more information on this topic, see Referencing Relations on Related Objects.
Related Sections
Learn about how to model object to object relationships in OpenFGA.
See how to make folders parents of documents, and to make editors on the parent folders editors on documents inside them..