Skip to main content

Auth0 FGA Consistency Modes

Background

The Auth0 FGA service has been optimized for lower latency and high availability rather than strong consistency on all read requests and evaluation queries. That means that results from the Read and Check, ListObjects, ListUsers and Expand 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:

Query Consistency Modes

To reduce latency, Auth0 FGA implements a multi-layer cache. It uses a database-level cache, which caches database reads, and a sub-problem cache, which caches partial query evaluations that can be reused across requests. Each cache has a TTL of 10 seconds. That means if a certain tuple is read from the DB, is then deleted and then subsequently read - the result of the first read might be returned for up to 20 seconds after the first call.

When querying Auth0 FGA using Read or any of the query APIs like Check, Expand, ListObjects and ListUsers, you can specify a query consistency parameter that can have one of the following values:

NameDescription
MINIMIZE_LATENCY (default)Auth0 FGA will serve queries from the cache when possible
HIGHER_CONSISTENCYAuth0 FGA will skip the cache and query the database directly

If you write a tuple and you immediately make a Check on a relation affected by that tuple using MINIMIZE_LATENCY, the tuple change might not be taken in consideration if Auth0 FGA serves the result from the cache.

When to use higher consistency

When specifying HIGHER_CONSISTENCY you are trading off consistency for latency and system performance. Always specifying HIGHER_CONSISTENCY will have a significant impact in performance.

If you have a use case where higher consistency is needed, it's recommended that whenever possible, you decide in runtime the consistency level you need. If you are storing a timestamp indicating when a resource was last modified in your database, you can use that to decide the kind of request you do.

For example, if you share document:readme with a user:anne and you update a modified_date field in the document table when that happens, you can write code like the below when calling check("user:anne", "can_view", "document:readme") to avoid paying the price of additional latency when calling the API.

if (date_modified + cache_time_to_live_period > Date.now()) {
const { allowed } = await fgaClient.check(
{ user: "user:anne", relation: "can_view", object: "document:roadmap"}
);
} else {
const { allowed } = await fgaClient.check(
{ user: "user:anne", relation: "can_view", object: "document:roadmap"},
{ consistency: ConsistencyPreference.HigherConsistency }
);
}

Eventually consistent, multi-region active-active database

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

When you specify the HIGHER_CONSISTENCY parameter in queries, Auth0 FGA will not use the cache, but it might still be affected by replication lag.

If your application services are hosted in a single cloud region, all the API requests will be always routed to the same Auth0 FGA region, and the replication lag will be negligible. However, if you have servers in multiple regions, Auth0 FGA might route requests to different regions, and the replication lag can still impact how consistent the API response is.

For example, assuming the following authorization model

model
schema 1.1

type user

type document
relations
define viewer: [user]
define can_view: viewer

Immediately after writing a tuple saying that Bob is a viewer of document:meeting_notes.doc, calling Check to see whether Bob can view document:meeting_notes.doc may return false.

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 = 'auth.fga.dev'
// 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,
},
},
});

await fgaClient.write({
writes: [
{"user":"user:bob","relation":"viewer","object":"document:meeting_notes.doc"}
],
}, {
authorizationModelId: "01HVMMBCMGZNT3SED4Z17ECXCA"
});
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 = 'auth.fga.dev'
// 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:bob',
relation: 'can_view',
object: 'document:meeting_notes.doc',
}, {
authorizationModelId: '01HVMMBCMGZNT3SED4Z17ECXCA',
});

// 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.

Check, Read and Expand

Comparison Between Check, Read And Expand API Calls.

Have Feedback?

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