Skip to main content

Object to Object Relationships

note
Auth0 Fine Grained Authorization (FGA) is the early-stage product we are building at Auth0 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 Auth0 FGA during the Developer Community Preview can be found here.

In this guide you'll learn how to model your application with objects that are not specifically tied to a user. For example, a folder is a parent of a document.

When to use

This design pattern is helpful in the case where there are relationships between different objects. With Auth0 Fine Grained Authorization (FGA), so long as both objects are in a type defined in the authorization model, relationship tuples can be added to indicate a relationship between them.

For example:

  • communities can contain channels
  • channels can contain posts
  • channels can contain threads
  • threads can contain posts
  • bookshelf can have books
  • trips can have bookings
  • account can contain transactions
  • buildings can have doors

Before you start

To better follow this guide, make sure you're familiar with some Auth0 FGA Concepts and know how to develop the things listed below.

You will start with the authorization model below, it represents a document type that can have users related as editor, and folder type that can have users related as viewer.

type document
relations
define editor as self
type folder
relations
define viewer as self

In addition, you will need to know the following:

Modeling User Groups

You need to know how to add users to groups and grant groups access to resources. Learn more →

Auth0 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 objects of this type and other users 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 Auth0 FGA

The Playground

Try this guide out on the Auth0 FGA Playground

Step by Step

01. Create parent relations in document

To represent that a folder can be a parent of a document, we first need to modify our document type definition to allow a parent relation.

type document
relations
define parent as self
define editor as self
type folder
relations
define viewer as self

02. Add parent relationship tuples

Once the type definition is updated, we can now create the relationship between a folder as a parent of a document. To do this, we will create a new relationship tuple that describes: folder:budgets is a parent of document:may_budget.doc. In Auth0 Fine Grained Authorization (FGA), users in the relationship tuples can not only be be IDs, but also other objects in the form of type:object_id.

Initialize the SDK
// FGA_ENVIRONMENT can be "us" (default if not set) for Developer Community Preview or "playground" for the Playground API
// import the SDK
const { Auth0FgaApi } = require('@auth0/fga');

// Initialize the SDK
const fgaClient = new Auth0FgaApi({
environment: process.env.FGA_ENVIRONMENT,
storeId: process.env.FGA_STORE_ID,
clientId: process.env.FGA_CLIENT_ID,
clientSecret: process.env.FGA_CLIENT_SECRET,
});

await fgaClient.write({
writes: {
tuple_keys: [
// The user in this case is another object where the type is `folder` and the object_id is `budgets`
{ user: 'folder:budgets', relation: 'parent', object: 'document:may_budget.doc'}
]
}
});
caution

Note: Auth0 FGA does not restrict what value belongs within the user key, so technically a user or any other object can be a parent of a document. It is your responsibility to ensure that relationship tuples are being created accordingly within your applications business logic.

03. Check that parent folders have permissions

Once that relationship tuple is added to Auth0 FGA, we can check if the relationship is valid by asking the following: "is folder:budgets a parent of document:may_budget.doc?"

Initialize the SDK
// FGA_ENVIRONMENT can be "us" (default if not set) for Developer Community Preview or "playground" for the Playground API
// import the SDK
const { Auth0FgaApi } = require('@auth0/fga');

// Initialize the SDK
const fgaClient = new Auth0FgaApi({
environment: process.env.FGA_ENVIRONMENT,
storeId: process.env.FGA_STORE_ID,
clientId: process.env.FGA_CLIENT_ID,
clientSecret: process.env.FGA_CLIENT_SECRET,
});

// Run a check
const { allowed } = await fgaClient.check({
tuple_key: {
user: 'folder:budgets',
relation: 'parent',
object: 'document:may_budget.doc',
},});

// allowed = true

It is important to note that the current authorization model does not imply inheritance of permissions. Even though folder:budgets is a parent of document:may_budget.doc, it does not inherit the editor relation from parent to document. Meaning editors on folder:budgets are not editors on document:may_budget.doc. Further configuration changes are needed to indicate that and will be tackled in a later guide.

caution

Note: When creating relationship tuples for Auth0 FGA make sure to use unique ids for each object and user within your application domain. We are using first names and simple ids to just illustrate an easy-to-follow example.

Advanced Object to Object Relationships

Object to object can be used for more advanced use case, such as entitlements. An example use case is to allow subscribers to be entitled to different plans.

01. Create authorization model with object to object relationships

To do this, the authorization model will have two types - feature and plan.

type feature
relations
define associated_plan as self
define access as self or subscriber_member from associated_plan
type plan
relations
define subscriber_member as self

Type feature has two relations, associated_plan and access. Relation associated_plan allows associating plans with features while access defines who can access the feature. In our case, the access can be achieved either from

  • direct relationship via the keyword `self` or `this`
  • object to object relationship where a user can access because it is a subscriber_member of a particular plan AND that plan is associated with the feature.

02. Adding relationship tuples

To realize the relationship, we will need to add the following relationship tuples.

Initialize the SDK
// FGA_ENVIRONMENT can be "us" (default if not set) for Developer Community Preview or "playground" for the Playground API
// import the SDK
const { Auth0FgaApi } = require('@auth0/fga');

// Initialize the SDK
const fgaClient = new Auth0FgaApi({
environment: process.env.FGA_ENVIRONMENT,
storeId: process.env.FGA_STORE_ID,
clientId: process.env.FGA_CLIENT_ID,
clientSecret: process.env.FGA_CLIENT_SECRET,
});

await fgaClient.write({
writes: {
tuple_keys: [
// make anne as subscriber_member for plan:advanced
{ user: 'anne', relation: 'subscriber_member', object: 'plan:advanced'},
// The advanced plan is associated with the data preview feature
{ user: 'plan:advanced', relation: 'associated_plan', object: 'feature:data_preview'}
]
}
});

03. Check to see if access is allowed without direct relationship

To validate that the authorization model and relationship tuples are correct, we can ask the question:

Initialize the SDK
// FGA_ENVIRONMENT can be "us" (default if not set) for Developer Community Preview or "playground" for the Playground API
// import the SDK
const { Auth0FgaApi } = require('@auth0/fga');

// Initialize the SDK
const fgaClient = new Auth0FgaApi({
environment: process.env.FGA_ENVIRONMENT,
storeId: process.env.FGA_STORE_ID,
clientId: process.env.FGA_CLIENT_ID,
clientSecret: process.env.FGA_CLIENT_SECRET,
});

// Run a check
const { allowed } = await fgaClient.check({
tuple_key: {
user: 'anne',
relation: 'access',
object: 'feature:data_preview',
},});

// allowed = true

We see that anne is allowed to access feature:data_preview without requiring direct relationship.

04. Disassociating plan from feature

At any point in time, plan:advanced may be disassociated from feature:data_preview.

Initialize the SDK
// FGA_ENVIRONMENT can be "us" (default if not set) for Developer Community Preview or "playground" for the Playground API
// import the SDK
const { Auth0FgaApi } = require('@auth0/fga');

// Initialize the SDK
const fgaClient = new Auth0FgaApi({
environment: process.env.FGA_ENVIRONMENT,
storeId: process.env.FGA_STORE_ID,
clientId: process.env.FGA_CLIENT_ID,
clientSecret: process.env.FGA_CLIENT_SECRET,
});

await fgaClient.write({
deletes: {
tuple_keys : [
// Remove advanced plan from data preview feature
{ user: 'plan:advanced', relation: 'associated_plan', object: 'feature:data_preview'}
]
}
});

When this is the case, anne will no longer have access to feature:data_preview even though she is still a subscriber_member of plan:advanced.

Initialize the SDK
// FGA_ENVIRONMENT can be "us" (default if not set) for Developer Community Preview or "playground" for the Playground API
// import the SDK
const { Auth0FgaApi } = require('@auth0/fga');

// Initialize the SDK
const fgaClient = new Auth0FgaApi({
environment: process.env.FGA_ENVIRONMENT,
storeId: process.env.FGA_STORE_ID,
clientId: process.env.FGA_CLIENT_ID,
clientSecret: process.env.FGA_CLIENT_SECRET,
});

// Run a check
const { allowed } = await fgaClient.check({
tuple_key: {
user: 'anne',
relation: 'access',
object: 'feature:data_preview',
},});

// allowed = false
Initialize the SDK
// FGA_ENVIRONMENT can be "us" (default if not set) for Developer Community Preview or "playground" for the Playground API
// import the SDK
const { Auth0FgaApi } = require('@auth0/fga');

// Initialize the SDK
const fgaClient = new Auth0FgaApi({
environment: process.env.FGA_ENVIRONMENT,
storeId: process.env.FGA_STORE_ID,
clientId: process.env.FGA_CLIENT_ID,
clientSecret: process.env.FGA_CLIENT_SECRET,
});

// Run a check
const { allowed } = await fgaClient.check({
tuple_key: {
user: 'anne',
relation: 'subscriber_member',
object: 'plan:advanced',
},});

// allowed = true
Advanced Modeling Patterns: Entitlements

Learn how to model entitlement access patterns.

Modeling Parent-Child Relationships

Learn how to model parent and child relationships.

Modeling User Groups

Learn how to model user groups.

Have Feedback?

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