Skip to main content

Usersets

note
Fine Grained Authorization (FGA) is the early-stage product we are building at Okta to solve fine-grained authorization at scale. Sign up for the Developer Community Preview to try it out, and join our Discord community if you are interested in learning more about our plans.

Please note that at this point in time, it is not considered production-ready and does not come with any SLAs; availability and uptime are not guaranteed. Limitations of Okta FGA during the Developer Community Preview can be found here.

What Is A Userset?

A userset represents a set or collection of users.

Usersets can be used to indicate that a group of users in the system have a certain relation with an object. This can be used to assign permissions to groups of users rather than specific ones, allowing us to represent the permissions in our system using less tuples and granting us flexibility in granting or denying access in bulk.

In Okta FGA, usersets are represented via this notation: object#relation, where object is made up of a type and an object identifier. For example:

  • company:xyz#employee represents all users that are related to company:xyz as employee
  • tweet:12345#viewer represents all users that are related to tweet:12345 as viewer

How Do Check Requests Work With Usersets?

Imagine the following authorization model:

model
schema 1.1

type user

type org
relations
define member: [user]

type document
relations
define reader: [user, org#member]

Now let us assume that the store has the following tuples:

[
// Userset "Members of the xyz org" can read the budget document
{
"user": "org:xyz#member",
"relation": "reader",
"object": "document:budget",
},
// Anne is part of the userset "Members of the xyz org"
{
"user": "user:anne",
"relation": "member",
"object": "org:xyz",
},
]

If we call the check API to see if user anne has a reader relationship with document:budget, Okta FGA will check whether anne is part of the userset that does have a reader relationship. Because she is part of that userset, the request will return 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_SCHEME = 'https'
// FGA_API_HOST = 'api.us1.fga.dev' for Dev Preview and Early Access / 'api.playground.fga.dev' for the FGA Playground
// 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' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' for Dev Preview and Early Access / not needed for the FGA Playground
// 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 / not needed for the FGA Playground
// 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 / not needed for the FGA Playground

const fgaClient = new OpenFgaClient({
apiScheme: process.env.FGA_API_SCHEME,
apiHost: process.env.FGA_API_HOST,
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:anne',
relation: 'reader',
object: 'document:budget',
}, {
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
});

// allowed = true

How Do Expand Requests Work With Usersets?

Imagine the following authorization model:

model
schema 1.1

type user

type document
relations
define writer: [user, org#member]
define reader: [user, org#member] or writer

If we wanted to see which users and usersets have a reader relationship with document:budget, we can call the Expand API. The response will contain a userset tree where the leaf nodes are specific user IDs and usersets. For example:

{
"tree": {
"root": {
"type": "document:budget#reader",
"union": {
"nodes": [
{
"type": "document:budget#reader",
"leaf": {
"users": {
"users": ["user:bob"]
}
}
},
{
"type": "document:budget#reader",
"leaf": {
"computed": {
"userset": "document:budget#writer"
}
}
}
]
}
}
}
}

As you can see from the response above, with usersets we can express unions of user groups. We can also express intersections and exclusions.

Internals

Using the type definitions in the authorization model, some of the situations we can represent are:

  • that a user is not in a set of users having a certain relation to an object, even if a relationship tuple exists in the system. See Disabling Direct Relationships
  • that a user has a certain relationship with an object if they are in the union, intersection or exclusion of usersets.
  • that a user being in a set of users having a certain relation to an object can result in them having another relation to the object. See Concentric Relationships
  • that the user being in a set of users having a certain relation to an object and that object is in a set of users having a certain relation to another object, can imply that the original user has a certain relationship to the final object. See Object-to-Object Relationships

When executing the Check API of the form check(user, relation, object), Okta FGA will perform the following steps:

  1. In the authorization model, look up type and its relation. Start building a tree where the root node will be the definition of that relation, which can be a union, exclusion, or intersection of usersets, or it can be direct users.
  2. Expand all the usersets involved into new nodes in the tree. This means recursively finding all the users that are members of the usersets. If there are direct relationships with users, create leaf nodes.
  3. Check whether user is a leaf node in the tree. If the API finds one match, it will return immediately and will not expand the remaining nodes.

Image showing the path <ProductName format={ProductNameFormat.ShortForm}/> traverses to find if a user is in the userset related to an object

Managing Group Membership

How to add users to a userset

Managing Group Access

How to add permissions to a userset

Have Feedback?

Join us on the Discord community if you have any questions or suggestions.