Define your application's resources with resource types.
Resource types are the basic building block of any authorization scheme in FGA. Represented as JSON, each resource type defines a type of resource (e.g. store, item, etc.) in your application along with the relationships that other resources (e.g. user) can have on resources of that type.
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" } ] } } }
FGA provides templates some resource types to make it easy to implement common use-cases like role-based access control, organization & team-based permissions, and pricing-tiers & feature entitlements without the need for much initial configuration of resource types. These resource types also serve as a great starting point for more complex authorization use cases and can easily be modified to better suit the needs of an application.
In most cases, applications require access control rules to be defined per user. To make this easier, FGA comes with a built-in user
resource type. The full representation of the user resource type is:
{ "type": "user", "relations": {} }
Most multitenant B2B applications have a concept of tenants: a way to partition data and users between customers. Some applications might refer to a tenant as an organization, a customer, a company, or one of many other alternatives. FGA helps enforce data isolation and access control across tenants in multitenant B2B applications by allowing you to specify authorization rules per tenant in your application. The full representation of the tenant resource type is:
{ "type": "tenant", "relations": { "admin": {}, "manager": { "inherit_if": "admin" }, "member": { "inherit_if": "manager" } } }
Roles are one of the core building blocks of role-based access control. They can be thought of as ‘containers’ or ‘groups’ of (typically) users. In most RBAC implementations, the set of roles is finite and usually based on some organizational structure and/or role (ex. admin, owner, member etc.). The role
resource type in FGA has a member
relation for designating that a user (or in some cases, another role) is a member of a particular role. The full representation of the resource type is:
{ "type": "role", "relations": { "member": { "inherit_if": "member", "of_type": "role", "with_relation": "member" } } }
Permissions are the second building block for implementing role-based access control. Permissions typically represent specific abilities or actions (e.g. creating a report, editing a report, etc.) that can be taken within an application. The permission
resource in FGA has a member
relation for designating that a role (or user) has that particular permission. Permissions are typically assigned to roles and then the roles are assigned to users. However, sometimes permissions can be assigned directly to users. The full representation of the permission
resource type is:
{ "type": "permission", "relations": { "member": { "inherit_if": "any_of", "rules": [ { "inherit_if": "member", "of_type": "permission", "with_relation": "member" }, { "inherit_if": "member", "of_type": "role", "with_relation": "member" } ] } } }
Pricing tiers represent specific ‘packages’ (or ‘tiers’) of features within an application. They can be considered similar to roles in RBAC and can be assigned to specific users and/or tenants to grant them access to varying levels of features based on their subscription/payment plan. Pricing tiers are typically used in SaaS applications to implement and manage different pricing plans (ex. ‘free’, ‘growth’, ‘enterprise’). The full representation of the pricing-tier
resource type is:
{ "type": "pricing-tier", "relations": { "member": { "inherit_if": "any_of", "rules": [ { "inherit_if": "member", "of_type": "pricing-tier", "with_relation": "member" }, { "inherit_if": "member", "of_type": "tenant", "with_relation": "member" } ] } } }
Features represent specific features in an application (e.g. analytics_dashboard
, report_builder
, etc.) and can be used to implement paid feature entitlements in conjunction with pricing tiers. Features are typically assigned to pricing tiers and those pricing tiers are assigned to tenants or users to grant them access to specific features in an application based on their subscription/payment plan. Features can be considered similar to permissions in RBAC. The full representation of the feature
resource type is:
{ "type": "feature", "relations": { "member": { "inherit_if": "any_of", "rules": [ { "inherit_if": "member", "of_type": "feature", "with_relation": "member" }, { "inherit_if": "member", "of_type": "pricing-tier", "with_relation": "member" }, { "inherit_if": "member", "of_type": "tenant", "with_relation": "member" } ] } } }