Skip to main content

Modeling With Multiple Restrictions

In this guide we are going to model system that requires multiple authorizations before allowing users to perform actions on particular objects using Okta FGA. For example, users are allowed to delete a document if both of these conditions are met:

  • they are a member of the organization that owns the document
  • they have writer permissions on the document

In this way, we prevent other users from deleting such document.

When to use

This is useful when:

  • Limiting certain actions (such as deleting or reading sensitive document) to privileged users.
  • Adding restrictions and requiring multiple authorization paths before granting access.

Before You Start

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

You will start with the authorization model below, it represents a document type that can have users related as writer and organizations related as owner. Document's can_write relation is based on whether user is a writer to the document. The organization type can have users related as member.

Let us also assume that we have:

  • A document called "planning" owned by the ABC organization.
  • Becky is a member of the ABC organization.
  • Carl is a member of the XYZ organization.
  • Becky and Carl both have writer access to the "planning" document.
model
schema 1.1

type user

type document
relations
define owner: [organization]
define writer: [user]
define can_write: writer

type organization
relations
define member: [user]

The current state of the system is represented by the following relationship tuples being in the system already:

[// organization ABC is the owner of planning document
{
"_description": "organization ABC is the owner of planning document",
"user": "organization:ABC",
"relation": "owner",
"object": "document:planning"
}// Becky is a writer to the planning document
{
"_description": "Becky is a writer to the planning document",
"user": "user:becky",
"relation": "writer",
"object": "document:planning"
}// Carl is a writer to the planning document
{
"_description": "Carl is a writer to the planning document",
"user": "user:carl",
"relation": "writer",
"object": "document:planning"
}// Becky is a member of the organization ABC
{
"_description": "Becky is a member of the organization ABC",
"user": "user:becky",
"relation": "member",
"object": "organization:ABC"
}// Carl is a member of the organization XYZ
{
"_description": "Carl is a member of the organization XYZ",
"user": "user:carl",
"relation": "member",
"object": "organization:XYZ"
}]
info

Note that we assign the organization, not the organization's members, as owner to the planning document.


In addition, you will need to know the following:

Modeling Parent-Child Objects

You need to know how to model access based on parent-child relationships, e.g.: folders and documents. Learn more →

Modeling Roles And Permissions

You need to know how to model roles for users at the object level and model permissions for those roles. Learn more →

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: 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 Okta FGA
  • Intersection Operator: the intersection operator can be used to indicate a relationship exists if the user is in all the sets of users

Step By Step

With the above authorization model and relationship tuples, Okta Fine Grained Authorization (FGA) will correctly respond with {"allowed":true} when *check*is called to see if Carl and Becky can write this document.

We can verify that by issuing two check requests:

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:becky',
relation: 'can_write',
object: 'document:planning',
}, {
authorization_model_id: '01HVMMBCMGZNT3SED4Z17ECXCA',
});

// 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:carl',
relation: 'can_write',
object: 'document:planning',
}, {
authorization_model_id: '01HVMMBCMGZNT3SED4Z17ECXCA',
});

// allowed = true

What we would like to do is offer a way so that a document can be written by Becky and Carl, but only writers who are also members of the organization that owns the document can remove it.

To do this, we need to:

  1. Add can_delete relation to only allow writers that are members of the ownership organization
  2. Verify that our solutions work

01. Add can_delete Relation To Only Allow Writers That Are Members Of The Ownership Organization

The first step is to add the relation definition for can_delete so that it requires users to be both writer and member of the owner. This is accomplished via the keyword and.

model
schema 1.1

type user

type document
relations
define owner: [organization]
define writer: [user]
define can_write: writer
define can_delete: writer and member from owner

type organization
relations
define member: [user]

02. Verify That Our Solutions Work

To verify that our solutions work, we need to check that Becky can delete the planning document because she is a writer AND she is a member of organization:ABC that owns the planning document.

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:becky',
relation: 'can_delete',
object: 'document:planning',
}, {
authorization_model_id: '01HVMMBCMGZNT3SED4Z17ECXCA',
});

// allowed = true

However, Carl cannot delete the planning document because although he is a writer, Carl is not a member of organization:ABC that owns the planning document.

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:carl',
relation: 'can_delete',
object: 'document:planning',
}, {
authorization_model_id: '01HVMMBCMGZNT3SED4Z17ECXCA',
});

// allowed = false
Modeling: User Groups

Learn about how to add group members.

Modeling: Blocklists

Learn about how to set block lists.

Modeling: Public Access

Learn about model public access.

Have Feedback?

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