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
ownerof the document. - Direct
viewerof the document. - A user who is a
memberof anorganizationthat has amemberrelation to theuser(this is a bit redundant with the directviewerbut shows the syntax). - A user who is a
memberof anorganizationthat is aparentof anotherorganization, and thatorganizationhasmemberrelations to theuser.
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:
- Is Bob a direct
ownerofdocument:doc1? No. - Is Bob a direct
viewerofdocument:doc1? No. - Is Bob a
memberof anorganizationthat has amemberrelation todocument:doc1? No. - Is Bob a
memberof anorganizationthat is aparentof anotherorganization, and thatorganizationhasmemberrelations to Bob? Let’s trace:- Bob is a
memberoforg:acme. - Is
org:acmeaparentof any organization? Yes,org:beta. - Is Bob a
memberoforg:beta? No.
- Bob is a
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:
- Is Bob a direct
owner? No. - Is Bob a direct
viewer? No. - Is Bob a
memberof anorganizationthat has amemberrelation todocument:doc1? Yes! Bob is amemberoforg:acme, andorg:acmehas amemberrelation todocument: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.