Skip to main content

Eventual Consistency when Performing Checks, Reads and Expand

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.

Background

The Auth0 FGA has been optimized for lower latency and high availability on all read requests rather than strong consistency. That means that results from the check, expand and read requests may not reflect the latest state and what was just written in the database, and might take a few seconds to converge.

Some of the choices that Auth0 FGA has made include:

Eventually consistent, multi-region active-active database

The Auth0 FGA service uses an eventually consistent database in its implementation, that means that if a read request is performed on a region different than the original write request directly after the write request was made, the data returned by the read might not reflect the data written by the write.

For example, assuming the following authorization model

type document
relations
define viewer as self
define can_view as viewer

Immediately after writing a tuple of Bob is a viewer of document:meeting_notes.doc, calling check request on whether Bob can view document:meeting_notes.doc may return 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,
});

await fgaClient.write({
writes: {
tuple_keys: [
{ user: 'bob', relation: 'viewer', object: 'document:meeting_notes.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: 'bob',
relation: 'can_view',
object: 'document:meeting_notes.doc',
},});

// allowed = false

Image showing call flow

Call flow for check and write. Notice that before cross-region replication happens, check will return false even though the tuple has already been written.

Caching DB requests & evaluations

To further reduce latency, Auth0 FGA is configured to cache certain evaluations for a brief period of time.

That means if a certain tuple is read (externally or internally) from the DB, is then modified by a write and then subsequently read - the result of the first read will be returned for up to 10 seconds after the first call.

So calling the following check request

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: 'bob',
relation: 'can_view',
object: 'document:meeting_notes.doc',
},});

// allowed = false

Will result in Auth0 FGA returning false to that same subsequent check for up to 10 seconds, even if the tuple (bob, reader, document:meeting_notes.doc) was written and replicated during that time. Additionally any further read requests will not return the (bob, reader, document:meeting_notes.doc) tuple till 10 seconds have passed from the original check request.

Implication

We have talked to a few customers and others in the industry regarding the need for strong consistency and concepts in the Zanzibar paper alluding to it (e.g. Zookies), and found that while strong consistency is important to some customers, availability and low latency are the essential requirements that users need.

You must take into consideration the eventually consistent design of Auth0 FGA when designing your services. Do not expect to write and then read that same data shortly after.

In most cases, when users share a resource, by the time they have sent the link and the recipient has clicked on it, the stale cache would have cleared.

Also watch out that the same applies when revoking access. What we found is that for many customers, they are fine with a user having access for a few seconds after access was revoked, but that may not be true for your use-case.

In case this is an issue for you, please reach out to us and we can discuss alternative solutions.

Check, Read and Expand

Comparison Between Check, Read And Expand API Calls.

Have Feedback?

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