Skip to main content

Roles and Permissions

Roles and permissions can be modeled within OpenFGA using an authorization model and relationship tuples.

  • Roles are assigned to users or a group of users. Any user can have more than one role, like editor or owner.
  • Permissions allow users to access certain objects based on their specific roles, like device_renamer or channel_archiver.

For example, the role viewer of a trip can have permissions to view bookings, while the role owners can have permissions to add or view trip bookings.

When to use a Roles and Permissions model

Role and permissions models in OpenFGA can both directly assign roles to users and assign permissions through relations users receive downstream from other relations. For example, you can:

  • Grant someone an admin role that can edit and read a document
  • Grant someone a security_guard role that can live_video_viewer on a device
  • Grant someone a viewer role that can view_products on a shop

Implementing a Roles and Permissions model allows existing roles to have finer-grained permissions, allowing your application to check whether a user has access to a certain object without having to explicitly check that specific users role. In addition, you can add new roles/permissions or consolidate roles without affecting your application behavior. For example, if your app's checks are for the fine permissions, like check('bob', 'booking_adder', 'trip:Europe') instead of check('bob', 'owner', 'trip:Europe'), and you later decide owners can no longer add bookings to a trip, you can remove the relation within the trip type with no code changes in your application and all permissions will automatically honor the change.

Before you start

Familiarize yourself with the basics of OpenFGA Concepts.

Assume that you have the following authorization model and a type called trip that users can be related to as viewer and/or an owner.

model
schema 1.1

type user

type trip
relations
define owner: [user]
define viewer: [user]

In addition, you need to know the following:

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
  • A Relationship: OpenFGA will be called to check if there is a relationship between a user and an object, indicating that the access is allowed
  • Union Operator: can be used to indicate that the user has multiple ways of being related to an object
  • Direct Relationship Type Restrictions: can be used to indicate direct relationships between users and objects
  • A Check API Request: used to check for relationships between users and objects

Step by step

The Roles and Permissions example below is a trip booking system that has owners and/or viewers, both of which can have more granular permissions like adding bookings to a trip or viewing a trip's bookings.

To represent this in an OpenFGA environment, you need to:

  1. Understand how roles are related to direct relations for the trip booking system
  2. Add implied relations to the existing authorization model to define permissions for bookings
  3. Check user roles and their permissions based on relationship tuples for direct and implied relations

01. Understand how roles work within the trip booking system

Roles are relations that are directly assigned to users. Below, the stated roles that a given user can be assigned are owner and viewer.

model
schema 1.1

type user

type trip
relations
define owner: [user]
define viewer: [user]

02. Add permissions for bookings

Permissions are relations that users get through other relations. To avoid adding a direct relationship type restriction to the relation in the authorization model while representing permissions, they instead define the relation via other relations in the model, which indicates that it is a permission granted to and implied from a different relation.

To add permissions related to bookings, add new relations to the trip object type denoting the various actions a user can take on trips, like view, edit, delete, or rename.

To allow viewers of a trip to have permissions to view bookings and owners to have permissions to add/view bookings, you modify the type:

model
schema 1.1

type user

type trip
relations
define owner: [user]
define viewer: [user]
define booking_adder: owner
define booking_viewer: viewer or owner

Note: both booking_viewer and booking_adder don't have direct relationship type restrictions, which ensures that the relation can only be assigned through the role and not directly.

03. Check user roles and their permissions

Your type definitions reflects the roles and permissions on how bookings can be viewed/added, so you can create relationship tuples to assign roles to users, then check if users have the proper permissions.

Create two relationship tuples:

  1. give bob the role of viewer on trip called Europe.
  2. give alice the role of owner on trip called Europe.
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: [
// Add bob as viewer on trip:Europe
{"_description":"Add bob as viewer on trip:Europe","user":"user:bob","relation":"viewer","object":"trip:Europe"},
// Add alice as owner on trip:Europe
{"_description":"Add alice as owner on trip:Europe","user":"user:alice","relation":"owner","object":"trip:Europe"}
],
}, {
authorization_model_id: "01HVMMBCMGZNT3SED4Z17ECXCA"
});

Now check: is bob allowed to view bookings on trip Europe?

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: 'booking_viewer',
object: 'trip:Europe',
}, {
authorization_model_id: '01HVMMBCMGZNT3SED4Z17ECXCA',
});

// allowed = true

bob is a booking_viewer because of the following chain of resolution:

  1. bob is a viewer on trip: Europe
  2. Any user related to the object trip:Europe as viewer is also related as a booking_viewer (i.e usersRelatedToObjectAs: viewer)
  3. Therefore, all viewers on a given trip are booking_viewers

To confirm that bob is not allowed to add bookings on trip Europe, run the following check:

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: 'booking_adder',
object: 'trip:Europe',
}, {
authorization_model_id: '01HVMMBCMGZNT3SED4Z17ECXCA',
});

// allowed = false

You also check: is alice allowed to view and add bookings on trip Europe?

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:alice',
relation: 'booking_viewer',
object: 'trip:Europe',
}, {
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:alice',
relation: 'booking_adder',
object: 'trip:Europe',
}, {
authorization_model_id: '01HVMMBCMGZNT3SED4Z17ECXCA',
});

// allowed = true

alice is a booking_viewer and booking_adder because of the following chain of resolution:

  1. alice is a owner on trip: Europe
  2. Any user related to the object trip:Europe as owner is also related as a booking_viewer
  3. Any user related to the object trip:Europe as owner is also related as a booking_adder
  4. Therefore, all owners on a given trip are booking_viewers and booking_adders on that trip
caution

Use unique ids for each object and user within your application domain when creating relationship tuples for OpenFGA. This example first names and simple ids as an easy-to-follow example.

Modeling Concepts: Concentric Relationships

Learn about how to represent a concentric relationships in OpenFGA.

Modeling Google Drive

See how to indicate that editors are commenters and viewers in Google Drive.

Modeling GitHub

See how to indicate that repository admins are writers and readers in GitHub.