Skip to main content

Modeling Roles and Permissions

To model roles and permissions in Okta FGA using authorization models and relationship tuples. For the purpose of this guide:

  • Roles are assigned to users or groups of users, where 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 has permissions to add or view bookings to a trip.

When to use

When creating a role and permissions model within Okta FGA, you should:

  • 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 Okta FGA, such as:

  • Breaking down existing roles to have more fine grained permission, which enables your application to check whether a user has access to a certain object without having to explicitly check that specific user's role.
  • Introduce new roles/permissions or consolidate roles without affecting your application behavior. For example: if your app's checks are for the fine permissions 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 the permissions will automatically honor the change.

Before you start

Familiarize yourself with the Okta FGA Concepts.

Assume you have the following authorization model
and that you have a type called trip that users can be related to as viewer and/or as 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

Learn how to create an authorization model and create a relationship tuple to grant a user access to an object. For more information, see Direct Access.

Okta FGA 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 consisting of a user, a relation, and an object stored in Auth Okta FGA
  • A Relationship: Okta FGA 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: used to check for relationships between users and objects

The Playground

Try this guide out on the Okta FGA Playground

Step By Step

To illustrate modeling Roles and Permissions in Okta FGA, imagine a trip booking system that has owners and/or viewers. It can also have granular permissions like adding bookings or viewing bookings on a trip.

To represent this, 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 Our Trip Booking System

In Okta FGA, roles are relations that can be directly assigned to users. In this authorization model, your roles are owner and viewer, so 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. Add Permissions For Bookings

In Okta Fine Grained Authorization (FGA), permissions are relations that users get only through other relations. To represent permissions, define the relation by other relations to indicate that it is a permission both granted to and implied from a different relation. Doing so avoids adding a direct relationship type restriction to the relation in the authorization model.

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, or delete.

To allow viewers of a trip to view bookings and owners to add/view bookings, modify the type as seen below:

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. This ensures that the relation can only be assigned through the role and not directly.

03. Checking User Roles And Their Permissions

Now that your type definitions reflect the roles and permissions governing how bookings can be viewed and added, create relationship tuples to assign roles to users, then check if users have the proper permissions.

Create two relationship tuples:

  1. gives bob the role of viewer on trip:Europe.
  2. gives alice the role of owner on trip:Europe.
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_URL = 'https://api.us1.fga.dev' // 'https://api.eu1.fga.dev' for EU and 'https://api.au1.fga.dev' for AU
// 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'
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' // 'https://api.eu1.fga.dev/' for EU and 'https://api.au1.fga.dev/' for AU
// 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
// 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

const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL,
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: [
// 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: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});

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

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_URL = 'https://api.us1.fga.dev' // 'https://api.eu1.fga.dev' for EU and 'https://api.au1.fga.dev' for AU
// 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'
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' // 'https://api.eu1.fga.dev/' for EU and 'https://api.au1.fga.dev/' for AU
// 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
// 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

const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL,
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,
},
},
});

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

// 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, perform the following check:

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_URL = 'https://api.us1.fga.dev' // 'https://api.eu1.fga.dev' for EU and 'https://api.au1.fga.dev' for AU
// 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'
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' // 'https://api.eu1.fga.dev/' for EU and 'https://api.au1.fga.dev/' for AU
// 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
// 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

const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL,
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,
},
},
});

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

// allowed = false

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

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_URL = 'https://api.us1.fga.dev' // 'https://api.eu1.fga.dev' for EU and 'https://api.au1.fga.dev' for AU
// 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'
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' // 'https://api.eu1.fga.dev/' for EU and 'https://api.au1.fga.dev/' for AU
// 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
// 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

const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL,
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,
},
},
});

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

// allowed = true
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_URL = 'https://api.us1.fga.dev' // 'https://api.eu1.fga.dev' for EU and 'https://api.au1.fga.dev' for AU
// 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'
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' // 'https://api.eu1.fga.dev/' for EU and 'https://api.au1.fga.dev/' for AU
// 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
// 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

const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL,
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,
},
},
});

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

// 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: Use unique IDs for each object and user within your application domain when creating relationship tuples for Okta Fine Grained Authorization (FGA). This guide uses first names and simple IDs as an easy-to-follow example.

Modeling Concepts: Concentric Relationships

Learn about how to represent a concentric relationships in Okta FGA.

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.

Have Feedback?

You can use any of our support channels for any questions or suggestions you may have.