Skip to main content

Roles and Permissions

In this guide you will learn how to model roles and permissions model within OpenFGA using the authorization model and relationship tuple.

  • Roles are assigned to users or a group of users, where any user can have more than one role (editor, owner, etc..).
  • Permissions are what allows users to access certain objects based on their specific roles (device_renamer, channel_archiver, etc..).

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

When to use

When trying to create a role and permissions model within OpenFGA.:

  • Create roles by creating relations that can be directly assigned to users
  • Assign permissions by creating relations that users get through other relations

For example:

  • 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

There are advantages to implementing roles and permissions within OpenFGA, such as:

  • Breaking down existing roles to have more fine grained permissions. This allows your application to check whether a user has access to a certain object without having to explicitly check that specific users role.
  • Introduce new roles/permissions or consolidate roles without affecting your application behavior. For example: if in your app all the checks are for the fine permissions check('bob', 'booking_adder', 'trip:Europe') instead of check('bob', 'owner', 'trip:Europe'), and then 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 the permissions will automatically honor the change.

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.
You have 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 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 Auth OpenFGA
  • 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
  • Direct Relationship Type Restrictions: can be used to indicate direct relationships between users and objects
  • A Check API Request the Check API Request is used to check for relationships between users and objects

Step By Step

To illustrate modeling Roles and Permissions in OpenFGA, we will use a trip booking system where you can have owners and/or viewers that can have more granular permissions such as adding bookings to a trip or viewing bookings on it.

In order to represent this, we need to:

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

01. Understand How Roles Work Within Our Trip Booking System

Relating roles within OpenFGA can be best described as the following: Roles are relations that can be directly assigned to users. Looking at our authorization model, our roles would then be owner and viewer. Meaning that a specific user can be an owner and/or a viewer.

model
schema 1.1
type user
type trip
relations
define owner: [user]
define viewer: [user]

02. Adding Permissions For Bookings

Permissions within OpenFGA can be best described as the following: Permissions are relations that users get only through other relations. To represent permissions, we avoid adding a direct relationship type restriction to the relation in the authorization model. Instead, we define the relation from other relations to indicate that it is a permission granted to and implied from a different relation.

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

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

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: notice how both booking_viewer and booking_adder don't have direct relationship type restrictions. This is to ensure that the relation can only be assigned through the role and not directly.

03. Checking User Roles And Their Permissions

Now that our type definitions reflects the roles and permissions on how bookings can be viewed/added. Let's create relationship tuples to assign roles to users and then check if users have the proper permissions.

Let us create two relationship tuples:

  1. that gives bob the role of viewer on trip: Europe.
  2. that gives alice the role of owner on trip: Europe.
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');

// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});

await fgaClient.write({
writes: {
tuple_keys: [
// Add bob as viewer on trip:Europe
{ user: 'user:bob', relation: 'viewer', object: 'trip:Europe'},
// Add alice as owner on trip:Europe
{ user: 'user:alice', relation: 'owner', object: 'trip:Europe'}
]
},
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});

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

Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');

// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});

// Run a check
const { allowed } = await fgaClient.check({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:bob',
relation: 'booking_viewer',
object: 'trip:Europe',
},
});

// 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, we can do the following check:

Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');

// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});

// Run a check
const { allowed } = await fgaClient.check({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:bob',
relation: 'booking_adder',
object: 'trip:Europe',
},
});

// allowed = false

We can 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 { OpenFgaApi } = require('@openfga/sdk');

// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});

// Run a check
const { allowed } = await fgaClient.check({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:alice',
relation: 'booking_viewer',
object: 'trip:Europe',
},
});

// allowed = true
Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaApi } = require('@openfga/sdk');

// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaApi({
apiScheme: process.env.FGA_API_SCHEME, // Either "http" or "https", defaults to "https"
apiHost: process.env.FGA_API_HOST, // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example)
storeId: process.env.FGA_STORE_ID, // Either "http" or "https", defaults to "https"
});

// Run a check
const { allowed } = await fgaClient.check({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:alice',
relation: 'booking_adder',
object: 'trip:Europe',
},
});

// 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

Note: Make sure to use unique ids for each object and user within your application domain when creating relationship tuples for OpenFGA. We are using first names and simple ids to just illustrate 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.