Skip to main content

Managing Relationships Between Objects

In this guide you will learn how to grant a user access to a particular object through a relationship with another object.

When to use

Giving user access through a relationship with another object is helpful because it allows scaling as the number of object grows. For example:

  • organization that owns many repos
  • team that administers many documents

Before you start

In order to understand this guide correctly you must be familiar with some OpenFGA Concepts and know how to develop the things that we will list below.

Assume that you have the following authorization model

  • a repo type that can have a admin relation
model
schema 1.1

type user

type repo
relations
define admin: [user]

In addition, you will need to know the following:

Direct access

You need to know how to create an authorization model and create a relationship tuple to grant a user access to an object. Learn more →

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: 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
  • An Object: represents an entity in the system. Users' relationships to it can be define through relationship tuples and the authorization model
  • A Relationship Tuple: a grouping consisting of a user, a relation and an object stored in OpenFGA

Step by step

For the current model, a user can be related as an admin to an object of type repo. If we wanted to have Anne be related to two repos, repo:1 and repo:2, we would have to add two relationship tuples, like so:

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:anne","relation":"admin","object":"repo:1"},
{"user":"user:anne","relation":"admin","object":"repo:2"}
],
}, {
authorization_model_id: "01HVMMBCMGZNT3SED4Z17ECXCA"
});

In general, every time we wanted to add a new admin relationship to a repo we'd have to add a new tuple. This doesn't scale as the list of repos and users grows.

01. Modify authorization model

Another way of modeling this is to have an authorization model as follows:

model
schema 1.1

type user

type repo
relations
define admin: [user] or repo_admin from owner
define owner: [org]

type org
relations
define repo_admin: [user]

In this model, we have:

  • added a new type org with one relation repo_admin.
  • added a new relation owner for type repo.
  • re-defined the relation admin for repo. A user can be defined as an admin directly, as we have seen above, or through the repo_admin from owner clause. How this works, for example, is that if user is related as repo_admin to org:xyz, and org:xyz is related as owner to repo:1, then user is an admin of repo:1.

02. Adding relationship tuples where user is another object

With this model, we can add tuples representing that an org is the owner of a repo. By adding following relationship tuples, we are indicating that the xyz organization is the owner of repositories with IDs 1 and 2:

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":"org:xyz","relation":"owner","object":"repo:1"},
{"user":"org:xyz","relation":"owner","object":"repo:2"}
],
}, {
authorization_model_id: "01HVMMBCMGZNT3SED4Z17ECXCA"
});

03. Adding relationship tuples to the other object

Now, imagine we have a new user Becky. If we wanted to have Becky be the admin of all repos without having to add one tuple per repo, all we need to do is add one tuple that says that Becky is related as repo_admin to org:xyz.

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:becky","relation":"repo_admin","object":"org:xyz"}
],
}, {
authorization_model_id: "01HVMMBCMGZNT3SED4Z17ECXCA"
});

04. Validating user access

We can now verify that Becky an admin of all the repos owned by org:xyz:

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:becky',
relation: 'admin',
object: 'repo:1',
}, {
authorization_model_id: '01HVMMBCMGZNT3SED4Z17ECXCA',
});

// allowed = true
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:becky',
relation: 'admin',
object: 'repo:2',
}, {
authorization_model_id: '01HVMMBCMGZNT3SED4Z17ECXCA',
});

// allowed = true

05. Revoking access

Suppose now that we want to prevent users from being an admin of repo:1 via org:xyz. We can delete one tuple:

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({
deletes: [
{ user: 'org:xyz', relation: 'owner', object: 'repo:1'}
],
}, {
authorization_model_id: "01HVMMBCMGZNT3SED4Z17ECXCA"
});

With this change, we may now verify that Becky is no longer an admin of repo:1.

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:becky',
relation: 'admin',
object: 'repo:1',
}, {
authorization_model_id: '01HVMMBCMGZNT3SED4Z17ECXCA',
});

// allowed = false
Modeling Parent-Child Objects

Learn about how to cascade relationships from parent object to child object.

Modeling Object to Object Relationships

Learn about modeling patterns on objects that are not specifically tied to a user.

Modeling GitHub

An example of object to object relationships.