Auth0 FGA is surprisingly not about who can do what, but rather about how we can efficiently check if someone can do something, by modeling relationships between entities.

Let’s see how it works with a concrete example. Imagine a simple document management system. We have users, documents, and organizations. A user might be an "owner" of a document, or a "viewer" in an organization that owns a document.

Here’s a basic schema in Auth0 FGA:

model
  schema 1.1

type user
type document
  relations
    owner: [user]
    viewer: [user]

type organization
  relations
    member: [user]
    parent: [organization]

Now, let’s define some "authorization rules" or "assertions" about our data. This is where we tell FGA about the actual relationships.

write users user:alice
write users user:bob
write users user:charlie

write documents document:doc1
write documents document:doc2

write organizations org:acme
write organizations org:beta

// Alice owns doc1
write relations user:alice relation owner relation document:doc1

// Bob is a member of org:acme
write relations user:bob relation member relation organization:acme

// Charlie is a member of org:beta
write relations user:charlie relation member relation organization:beta

// org:acme is parent of org:beta
write relations organization:acme relation parent relation organization:beta

With this setup, we can ask Auth0 FGA questions. For instance, "Can Alice view document:doc1?"

{
  "user": "user:alice",
  "relation": "viewer",
  "object": "document:doc1"
}

Auth0 FGA will evaluate this based on the rules. In this case, since Alice is the owner of document:doc1, and ownership implies the ability to view (we’ll define that next), the answer would be "yes".

The real power comes when we start defining more complex relationships. We can say that a user can view a document if they are the owner, OR if they are a member of an organization that is a parent of another organization, and that second organization has members who are viewers of the document. This is expressed in the model file by expanding our document type:

model
  schema 1.1

type user
type document
  relations
    owner: [user]
    viewer: [user]
    can_view: owner or viewer or member.organization->member.user or member.organization->member.organization->member.user // This is the key!

type organization
  relations
    member: [user]
    parent: [organization]

Here, can_view is a computed relation. It’s defined as:

  • Direct owner of the document.
  • Direct viewer of the document.
  • A user who is a member of an organization that has a member relation to the user (this is a bit redundant with the direct viewer but shows the syntax).
  • A user who is a member of an organization that is a parent of another organization, and that organization has member relations to the user.

This chaining (organization->member.user, organization->parent->organization->member.user) is what allows for very complex, hierarchical, and indirect authorization checks.

When we ask "Can Bob view document:doc1?", Auth0 FGA will trace the relationships:

  1. Is Bob a direct owner of document:doc1? No.
  2. Is Bob a direct viewer of document:doc1? No.
  3. Is Bob a member of an organization that has a member relation to document:doc1? No.
  4. Is Bob a member of an organization that is a parent of another organization, and that organization has member relations to Bob? Let’s trace:
    • Bob is a member of org:acme.
    • Is org:acme a parent of any organization? Yes, org:beta.
    • Is Bob a member of org:beta? No.

So, in this scenario, Bob cannot view document:doc1.

Now, let’s add a rule: document:doc1 is viewable by members of org:acme. We’d add this assertion:

// Members of org:acme can view doc1
write relations organization:acme relation member relation document:doc1

And update our model to include this new indirect relationship:

model
  schema 1.1

type user
type document
  relations
    owner: [user]
    viewer: [user]
    // Added 'organization.member' to the 'can_view' relation
    can_view: owner or viewer or organization.member or member.organization->member.user or member.organization->member.organization->member.user

type organization
  relations
    member: [user]
    parent: [organization]

Now, if we ask "Can Bob view document:doc1?", Auth0 FGA will check:

  1. Is Bob a direct owner? No.
  2. Is Bob a direct viewer? No.
  3. Is Bob a member of an organization that has a member relation to document:doc1? Yes! Bob is a member of org:acme, and org:acme has a member relation to document:doc1.

So, Bob can now view document:doc1.

The "surprising" part is how FGA abstracts away the underlying data store. You don’t JOIN tables or write complex SQL. You define a model of your entities and their relationships, write down assertions about those relationships, and then ask FGA "can this user do this?". FGA’s engine then efficiently resolves these queries, even across many levels of indirection.

What most people don’t realize is that the organization.member syntax in the can_view relation is a shortcut. It implicitly means "for any organization that has a member relation to this document, check if the user is a member of that organization." This is a powerful way to express group-based permissions where the group itself is an entity with relationships.

The next concept to explore is how to handle revoking permissions, especially when they are inherited through multiple layers of relationships.

Want structured learning?

Take the full Auth0 course →