Skip to main content

Migrating Relations

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 the lifecycle of software development, you will need to make updates or changes to the authorization model. In this guide, you will learn best practices for changing your existing authorization model. With these recommendations, you will minimize downtime and ensure your relationship models stay up to date.

Before You Start

This guide assumes you are familiar with the following 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 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 Auth0 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

The document below is an example of a relational authorization model. In this model, you can assign users to the editor relation. The editor relation has write privileges that regular users do not.

In this scenario, you will migrate the following model:

model
schema 1.1
type document
relations
define editor:
define can_edit: editor
type user

There are existing relationship tuples associated with editor relation.

[
{
"user": "user:anne",
"relation": "editor",
"object": "document:roadmap",
},
{
"user": "user:charles",
"relation": "editor",
"object": "document:roadmap",
},
]

This is the authorization model that you will want to migrate to:

model
schema 1.1
type document
relations
define writer:
define can_write: writer
type user

01. Create A Backwards Compatible Model

To avoid service disruption, you will create a backwards compatible model. The backwards compatible model ensures the existing relationship tuple will still work.

In the example below, user:Anne still has write privileges to the document:roadmap resource.

model
schema 1.1
type document
relations
define editor:
define writer: or editor
define can_write: writer
define can_edit: writer
type user

Test the can_edit definition. It should produce a value of true.

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({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:anne',
relation: 'can_write',
object: 'document:roadmap',
},
});

// allowed = true
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({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:anne',
relation: 'can_edit',
object: 'document:roadmap',
},
});

// allowed = true

02. Create a New Relationship Tuple

Now that you have a backwards compatible model, you can create new relationship tuples with a new relation.

In this example, you will add Bethany to the writer relationship.

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: [
// Bethany assigned writer instead of editor
{ user: 'user:bethany', relation: 'writer', object: 'document:roadmap'}
]
},
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});

Run a check in the API for Bethany to ensure correct access.

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({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:bethany',
relation: 'can_write',
object: 'document:roadmap',
},
});

// allowed = true

03. Migrate The Existing Relationship Tuples

Next, migrate the existing relationship tuples. The new relation makes this definition obsolete.

Use the read API to lookup all relationship tuples with the editor name in your model.

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,
});

// Execute a read
const { tuples } = await fgaClient.read({
tuple_key: {
relation:'editor',
object:'document:',
},
});

// tuples = [{"key": {"user":"user:anne","relation":"editor","object":"document:planning"}, "timestamp": "2021-10-06T15:32:11.128Z"},{"key": {"user":"user:charles","relation":"editor","object":"document:planning"}, "timestamp": "2021-10-06T15:32:11.128Z"}]

Update the new tuples with the write relationship.

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: [
{ user: 'user:anne', relation: 'writer', object: 'document:roadmap'},
{ user: 'user:charles', relation: 'writer', object: 'document:roadmap'}
]
},
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});

Finally, remove the old 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({
deletes: {
tuple_keys : [
{ user: 'user:anne', relation: 'editor', object: 'document:roadmap'},
{ user: 'user:charles', relation: 'editor', object: 'document:roadmap'}
]
},
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
info

Perform a write operation before a delete operation to ensure Anne still has access.

Confirm the tuples are correct by running a check on the user.

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({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:anne',
relation: 'can_write',
object: 'document:roadmap',
},
});

// allowed = true

The old relationship tuple no longer exists.

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({
authorization_model_id: '1uHxCSuTP0VKPYSnkq1pbb1jeZw',
tuple_key: {
user: 'user:anne',
relation: 'editor',
object: 'document:roadmap',
},
});

// allowed = false

04. Remove Obsolete Relationship From The Model

After you remove the previous relationship tuples, update your authorization model to remove the obsolete relation.

model
schema 1.1
type document
relations
define writer:
define can_write: writer
type user

Now, the write API will only accept the new relation name.

Transactional Writes

Learn how to perform transactional write

Relationship Queries

Understand the differences between check, read, expand and list objects.

Have Feedback?

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