Define your application's resources with resource types.
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 in a simple block of JSON. They can be created directly in the FGA dashboard or via the Resource Types API.
Let’s explore the various attributes of resource types by creating an 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:
{ "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. Relations 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:
{ "type": "user", "relations": { "manager": {} } }, { "type": "store", "relations": { "owner": {}, "editor": {}, "viewer": {} } }, { "type": "item", "relations": { "owner": {}, "editor": {}, "viewer": {}, "parent": {} } }
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.
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
{ "type": "user", "relations": { "manager": {} } }, { "type": "store", "relations": { "owner": {}, "editor": { "inherit_if": "owner" }, "viewer": { "inherit_if": "editor" } } }, { "type": "item", "relations": { "owner": {}, "editor": { "inherit_if": "owner" }, "viewer": { "inherit_if": "editor" }, "parent": {} } }
With our inherit_if
rules in place, we can simply grant a user the editor
relation and they will implicitly inherit the viewer
relation. inherit_if
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 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
. 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.
{ "type": "user", "relations": { "manager": {} } }, { "type": "store", "relations": { "owner": {}, "editor": { "inherit_if": "owner" }, "viewer": { "inherit_if": "editor" } } }, { "type": "item", "relations": { "owner": { "inherit_if": "owner", "of_type": "store", "with_relation": "parent" }, "editor": { "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": { "inherit_if": "editor" }, "parent": {} } }
The inherit_if
, of_type
, and with_relation
properties 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 operations 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
:
{ "type": "item", "relations": { "editor": {}, "viewer": {}, "editor-or-viewer": { "inherit_if": "any_of", "rules": [ { "inherit_if": "editor" }, { "inherit_if": "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
:
{ "type": "item", "relations": { "editor": {}, "viewer": {}, "editor-and-viewer": { "inherit_if": "all_of", "rules": [ { "inherit_if": "editor" }, { "inherit_if": "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
:
{ "type": "item", "relations": { "editor": {}, "viewer": {}, "not-editor-and-not-viewer": { "inherit_if": "none_of", "rules": [ { "inherit_if": "editor" }, { "inherit_if": "viewer" } ] } } }