Define application authorization logic independently from application code using a domain-specific language (DSL).
A schema is the core structure of an authorization model in FGA. It defines the types of resources, the relations between them, and the policies that govern access.
A schema can be represented in two formats:
apply
command with the CLI or on the FGA Dashboard.Schemas allow you to manage authorization logic independently from application logic. They can be versioned, stored in Git, and applied via the CLI:
workos fga schema apply ./schema.txt
Once applied, changes take effect immediately, meaning any updates to authorization logic will instantly reflect in subsequent permission checks and queries.
The JSON representation of a schema is the raw format that FGA uses to define resource types, relations, and inheritance rules. However, it can be verbose and difficult to read, especially for complex authorization models.
Consider the following examples:
{ "resource_types": [ { "type": "user", "relations": { "manager": { "allowed_types": ["user"] } } }, { "type": "store", "relations": { "owner": { "allowed_types": ["user"] }, "editor": { "allowed_types": ["user"], "inherit_if": "owner" }, "viewer": { "allowed_types": ["user"], "inherit_if": "editor" } } }, { "type": "item", "relations": { "owner": { "allowed_types": ["user"] "inherit_if": "owner", "of_type": "store", "with_relation": "parent" }, "editor": { "allowed_types": ["user"], "inherit_if": "any_of", "rules": [ { "inherit_if": "owner" }, { "inherit_if": "editor", "of_type": "store", "with_relation": "parent" }, { "inherit_if": "manager", "of_type": "user", "with_relation": "owner" } ] }, "viewer": { "allowed_types": ["user"], "inherit_if": "editor" }, "parent": { "allowed_types": ["store"] } } } ] }
version 0.3 type user relation manager [user] type store relation owner [user] relation viewer [user] inherit viewer if relation editor // editors are also viewers relation editor [user] inherit editor if relation owner type item // An item can have a parent store relation parent [store] relation owner [user] inherit owner if relation owner on parent [store] relation editor [user] inherit editor if any_of relation owner relation editor on parent [store] relation manager on owner [user] relation viewer [user] inherit viewer if relation editor
The FGA schema language representation is more concise, easier to read, and supports comments. These features make it simpler to define and manage complex authorization models in a more developer-friendly format.
Each schema must start with a version
declaration. This version declaration dictates the version of the schema language the transpiler will use to convert the schema into its JSON representation. As we add support for new features and functionality to the schema language, we will release new versions of it. Versioning the language in this way allows us to ensure backwards compatibility as we roll out these enhancements. See a full changelog of schema versions here.
version 0.3
Resource types are the basic building block of any authorization model in FGA. They are represented as JSON, but can be defined in a more developer-friendly format using the FGA schema language.
Each resource type defines a set of relationships that can exist on a specific type of resource (e.g. store, item, etc). These relationships can be assigned to other resources (e.g. user) known as subjects.
Resource types are an incredibly flexible way to define authorization models, allowing you to express complex hierarchical and inherited relationships. They can be created directly in the FGA dashboard, via the Resource Types API or by applying the schema with the CLI.
Let’s explore the various attributes of resource types by creating a schema-based authorization model for a simple e-commerce application that has three resource types: users, stores, and items.
The first attribute of a resource type is its type
. Each resource type must have a unique string as its type. Let’s start defining the resource types for our e-commerce application:
version 0.3 type user type store type item
With the basic definitions above, we’ve started building an authorization model for our application that will allow us to create fine grained access control rules for stores, items, and users, helping us answer questions like:
Does [user:1] have the ability to [edit] [item:x]? is [user:1] the [owner] of [store:3]?
In order to create access rules using our resource types, we first need to define the relationships available on a resource of that type. For example, if we want to specify that [user:A] is an [owner] of [store:S]
, we must add an owner
relation to the store
resource type.
By default, a subject can only have a relation on a resource explicitly. This means the relation must be explicitly granted via a warrant.
Let’s add some relations to our resource types.
In our application, a store can have owners
, editors
, and viewers
. owners
and editors
have more privileged access (like being able to modify details about a store) than viewers
(who have read-only access).
An item can have the same three relations as a store plus a fourth relation called parent
. This is because a store can be the parent
of an item, meaning the item belongs to that store. We’ll use this relation later to implement inherited relations on items.
Lastly, our user
resource type is relatively simple and has one relation: manager
. This is because a user can be the manager
of another user. We’ll use this relation later to enable inherited relations based on user hierarchies.
Let’s add these relations to our resource types:
version 0.3 type user relation manager [user] type store relation owner [user] relation editor [user] relation viewer [user] type item relation owner [user] relation editor [user] relation viewer [user] relation parent [store]
With these resource types, we can now create authorization rules that specify exactly which users are owners
, editors
, and viewers
of each store or item. We can also assign stores as parents
of items, and users as managers
of other users.
Use brackets []
in the schema language after the relation name for resource type checking. The resource type can be empty, a single type, or an array of types. Type checking occurs at runtime when your application calls the service create warrants. The server validates that warrants have proper subject types. The service also checks types when applying a new schema to confirm that inheritance rules are valid.
Use empty resource types to define computed relationships with no direct subjects. Such as a relation inherited from other relations.
Version 0.1
of the schema language does not support type safety on relations.
While only using explicitly assigned relations to build your authorization model can be powerful, creating warrants for each and every relationship in an application can become tedious or infeasible for larger, more complex use cases. That’s why relations can define rules under which they can be inherited (e.g. a user is an editor of a store if they're an owner of that store
).
There are two ways in which relations can be inherited:
In practice, it’s common for relations to have overlap (e.g. an owner
has the same privileges as an editor
+ additional privileges). For example, in many applications a user with write privileges inherits read privileges too.
In our example application, an owner
will inherit both the editor
and the viewer
relations, and an editor
will inherit the viewer
relation. Instead of having to explicitly assign each of the owner
, editor
, and viewer
relations to a user who is an owner
, resource types allow you to specify an inheritance hierarchy (e.g. the editor
relation is inherited if the user is an owner
) using the inherit_if
property.
Let’s add inherit_if
rules to our store
and item
resource types specifying that:
owners
are also editors
editors
are also viewers
version 0.3 type user relation manager [user] type store relation owner [user] relation editor [user] relation viewer [user] inherit viewer if relation editor inherit editor if relation owner type item relation owner [user] relation editor [user] relation viewer [user] relation parent [store] inherit viewer if relation editor inherit editor if relation owner
With our inherit <relation> if
rules in place, we can simply grant a user the editor
relation and they will implicitly inherit the viewer
relation. inherit
rules also work recursively on other inherited relations, so assigning a user the owner
relation will implicitly grant that user both the editor
and viewer
relations. This is because owner
will inherit editor
and editor
will in turn inherit viewer
.
This will simplify our access checks and cut down on the number of warrants we need to create for each user.
In many applications, resources themselves have a hierarchy (e.g. a document belongs to a folder, a user belongs to a team, a team belongs to an organization, etc.) and the access rules for these resources follow that hierarchy (e.g. the owner of a folder is the owner of any document in that folder).
Using the following two rules:
inherit <relation> if
relation <resource_type.relation> on <relation> [<resource_type>]
We can specify that a relation can be inherited when a user has a particular relation (<resource_type.relation>
) on another resource (<resource_type>
) that has a particular relation (<relation>
) on the resource we are checking access to.
For example, a user is an editor
of a document if they are an editor
of a folder
that is the document’s parent
. In our example app, let’s define the following three resource inheritance rules:
owner
of an item if that user is an owner
of a store
that is the item’s parent
.editor
of an item if that user is an editor
of a store
that is the item’s parent
.editor
of an item if that user is the manager
of the user
that is the item’s owner
.NOTE: Some of the relations below will be composing multiple inheritance rules together using logical operators. We’ll cover this in detail later.
version 0.3 type user relation manager [user] type store relation owner [user] relation editor [user] relation viewer [user] inherit viewer if relation editor inherit editor if relation owner type item relation owner [user] relation editor [user] relation viewer [user] relation parent [store] inherit owner if relation owner on parent [store] inherit editor if any_of relation owner relation editor on parent [store] relation manager on owner [user]) inherit viewer if relation editor
These rules make it easy to define inheritance rules for complex relationships between resources so we don’t have to create a large number of explicit warrants. Without them, we’d need to create a warrant for every item ↔ store ↔ user relationship in our application. This could easily be thousands, if not hundreds of thousands of rules.
With both the two types of relation inheritance rules in our toolkit, we can create authorization models for a majority of use cases, but there are still some scenarios that require a combination of these inheritance rules (e.g. a user is an editor
of an item if they are an owner
of that item OR they are the manager
of another user who is an editor
of that item).
To design authorization models that cover such scenarios, relations can compose multiple inheritance rules using logical operators to form more complex conditions.
The three supported logical operations are any_of
, all_of
, and none_of
.
The any_of
operation allows you to specify that a relation be inherited if at least one of the rules in the set is satisfied. In other words, it works like the logical OR operation.
The following resource type specifies an editor-or-viewer
relation that is inherited if the user is an editor
OR if the user is a viewer
:
version 0.3 type item relation editor [user] relation viewer [user] relation editor-or-viewer [] inherit editor-or-viewer if any_of relation editor relation viewer
The all_of
rule type allows you to specify that a relation be inherited if all of the rules in the set are satisfied. In other words, it works like the logical AND operation.
The following resource type specifies an editor-and-viewer
relation that is implicitly granted if the user is an editor
AND the user is a viewer
:
version 0.3 type item relation editor [user] relation viewer [user] relation editor-and-viewer [] inherit editor-and-viewer if all_of relation editor relation viewer
The none_of
rule type allows you to specify that a relation be inherited if none of the rules in the set are satisfied. In other words, it works like the logical NOR operation.
The following resource type specifies a not-editor-and-not-viewer
relation that is implicitly granted if the user is not an editor
AND the user is not a viewer
:
version 0.3 type item relation editor [user] relation viewer [user] relation not-editor-and-not-viewer [] inherit not-editor-and-not-viewer if none_of relation editor relation viewer
Define type restrictions on group warrants by joining the type and expected relation with a #
. For example, relation editor [group#member]
means that the editor
relation can be assigned to warrants where group
is the subject type and member
is the subject relation.
Group warrants are a special type of warrant that allow you to define exceptions to schema relationships at runtime. See the Group Warrant documentation for more details.
type user type group relation member [user] type document relation editor [group#member]
If your relation type defines a resource type and no group warrant types, it will default to allow all group warrants.
For example:
// Allows subject_type == "group" and subject_relation == null | <any_value> relation editor [group] // Allows subject_type == "group" and subject_relation == "member" relation editor [group#member] // Allows subject_type == "group" and subject_relation == "member" | "owner" relation editor [group#member, group#ownwer] // Allows subject_type == "group" and subject_relation == null | "member" relation editor [group, group#member]
Policies are a way to define custom logic that can be used in your schema. They allow you to create complex rules that go beyond simple relation inheritance. Policies can be defined using the policy
keyword and can include parameters, expressions, and logical conditions.
version 0.3 type user type group relation member [user] type asset relation service_manager [group] relation access_diagnostics [] inherit access_diagnostics if all_of relation member on service_manager [group] policy is_in_geo_fence policy is_in_geo_fence(user_location map, geofence map) { user_location.lat >= geofence.min_lat && user_location.lat <= geofence.max_lat && user_location.lon >= geofence.min_lon && user_location.lon <= geofence.max }
Read more about policies in the Policies documentation.
{ "resource_types": [ { "type": "user" }, { "type": "group" }, { "type": "store" }, { "type": "item" } ] }
{ "resource_types": [ { "type": "user", "relations": { "manager": { "allowed_types": ["user"] } } }, { "type": "store", "relations": { "owner": { "allowed_types": ["user"] }, "editor": { "allowed_types": ["user"] }, "viewer": { "allowed_types": ["user"] } } }, { "type": "item", "relations": { "owner": { "allowed_types": ["user"] }, "editor": { "allowed_types": ["user"] }, "viewer": { "allowed_types": ["user"] }, "parent": { "allowed_types": ["store"] } } } ] }
{ "resource_types": [ { "type": "user", "relations": { "manager": { "allowed_types": ["user"] } } }, { "type": "store", "relations": { "owner": { "allowed_types": ["user"] }, "editor": { "allowed_types": ["user"], "inherit_if": "owner" }, "viewer": { "allowed_types": ["user"], "inherit_if": "editor" } } }, { "type": "item", "relations": { "owner": { "allowed_types": ["user"] }, "editor": { "allowed_types": ["user"], "inherit_if": "owner" }, "viewer": { "allowed_types": ["user"], "inherit_if": "editor" }, "parent": { "allowed_types": ["store"] } } } ] }
{ "resource_types": [ { "type": "user", "relations": { "manager": { "allowed_types": ["user"] } } }, { "type": "store", "relations": { "owner": { "allowed_types": ["user"] }, "editor": { "allowed_types": ["user"], "inherit_if": "owner" }, "viewer": { "allowed_types": ["user"], "inherit_if": "editor" } } }, { "type": "item", "relations": { "owner": { "allowed_types": ["user"], "inherit_if": "owner", "of_type": "store", "with_relation": "parent" }, "editor": { "allowed_types": ["user"], "inherit_if": "any_of", "rules": [ { "inherit_if": "owner" }, { "inherit_if": "editor", "of_type": "store", "with_relation": "parent" }, { "inherit_if": "manager", "of_type": "user", "with_relation": "owner" } ] }, "viewer": { "allowed_types": ["user"], "inherit_if": "editor" }, "parent": { "allowed_types": ["store"] } } } ] }
Using the inherit_if
, of_type
, and with_relation
properties, we can specify that a relation can be inherited when a user has a particular relation (inherit_if
) on another resource (of_type
) that has a particular relation (with_relation
) on the resource we are checking access to. For example, a user is an editor
of a document if they are an editor
of a folder
that is the document’s parent
.
{ "resource_types": [ { "type": "item", "relations": { "editor-or-viewer": { "allowed_types": ["user"], "inherit_if": "any_of", "rules": [ { "inherit_if": "editor" }, { "inherit_if": "viewer" } ] }, "editor-and-viewer": { "allowed_types": ["user"], "inherit_if": "all_of", "rules": [ { "inherit_if": "editor" }, { "inherit_if": "viewer" } ] }, "not-editor-and-not-viewer": { "allowed_types": ["user"], "inherit_if": "none_of", "rules": [ { "inherit_if": "editor" }, { "inherit_if": "viewer" } ] } } } ] }
[ { "type": "group", "relations": { "member": { "allowed_types": ["user"] } } }, { "type": "document", "relations": { "editor": { "allowed_types": ["group#member"] } } } ]
{ "resource_types": [ { "type": "user" }, { "type": "group", "relations": { "member": { "allowed_types": ["user"] } } }, { "type": "asset", "relations": { "service_manager": { "allowed_types": ["group"] }, "access_diagnostics": { "allowed_types": [], "inherit_if": "all_of", "rules": [ { "inherit_if": "member", "of_type": "group", "with_relation": "service_manager" }, { "policy": "is_in_geo_fence" } ] } } } ], "policies": { "is_in_geo_fence": { "parameters": [ { "name": "user_location", "type": "map" }, { "name": "geofence", "type": "map" } ], "expression": "user_location.lat >= geofence.min_lat && user_location.lat <= geofence.max_lat && user_location.lon >= geofence.min_lon && user_location.lon <= geofence.max_lon" } } }
You can convert the FGA schema language to JSON using the workos fga schema convert
command. This command transpiles the schema language into its JSON representation, which can then be used with the FGA API.
workos fga schema convert schema.txt --to json --output raw > schema.json
version 0.3 type user type group relation member [user] type asset relation service_manager [group] inherit access_diagnostics if all_of relation member on service_manager [group] policy is_in_geo_fence policy is_in_geo_fence(user_location map, geofence map) { user_location.lat >= geofence.min_lat && user_location.lat <= geofence.max_lat && user_location.lon >= geofence.min_lon && user_location.lon <= geofence.max_lon }
version 0.2 type report relation parent [organization, organization#member] relation owner [user] relation editor [user]