Allow customers to create and assign org-scoped custom roles, mapping them to a static set of permissions available in your application.
Today’s B2B customers expect strong access controls in the software they buy. Each customer has a unique business structure with specific authorization needs. These needs shape the access each employee should have within the software.
By building customizable, role-based access control into your product, you create a powerful selling point. It gives customers the freedom to map roles to the exact permissions and capabilities they need.
To get the most out of this guide, you’ll need:
In this guide, we will build a fine grained authorization model for a B2B SaaS application with FGA. The application will allow customers to assign their users a per-organization role that grants access to various capabilities (permissions) in the product.
Customers can dynamically create roles and map them to a subset of the available permissions. These mappings will apply only to their customer organization.
This guide will explain how to:
Don't see an SDK you need? Contact us to request an SDK!
We’ll start by defining a resource type schema to represent relationships. Relationships dictate authorization between various resources in our application.
Our product requirements:
can_read_company_info
, can_write_company_info
, can_read_reports
, and can_write_reports
role:acme_admin
grants write_reports
on organization:acme
)role:acme_admin
)To summarize, these are the questions our authorization model should be able to answer:
U
have on organization O
?P
on organization O
?U
have permission P
on organization O
?To model our authorization requirements, let’s define the following schema:
version 0.2 type user type role relation member [user] type organization relation can_read_company_info [role] relation can_write_company_info [role] relation can_read_reports [role] relation can_write_reports [role] inherit can_read_company_info if any_of relation can_write_company_info relation member on can_read_company_info [role] inherit can_write_company_info if relation member on can_write_company_info [role] inherit can_read_reports if any_of relation can_write_reports relation member on can_read_reports [role] inherit can_write_reports if relation member on can_write_reports [role]
Create a file called schema.txt
containing the schema definition from above. Then use the CLI to apply this schema to your WorkOS FGA environment.
workos fga schema apply schema.txt
Define a resource type schema in the FGA dashboard using the schema editor available on the Schema page.
With our resource type schema defined, we need to assign warrants associating:
Let’s start by creating warrants. Warrants will associate a role with each of the organization permissions. We will then assign these roles to our users.
Our customers will be able to create custom roles and assign each role a set of permissions for their organization. To keep each role unique across all organizations, we’ll prefix each role’s identifier with its organization’s ID. This approach ensures that roles don’t overlap and makes permission checks straightforward.
curl "https://api.workos.com/fga/v1/warrants" \ -X POST \ -H "Authorization: Bearer sk_example_123456789" \ --data-raw \ '[ { "op": "create", "resource_type": "organization", "resource_id": "org_acme", "relation": "can_write_company_info", "subject": { "resource_type": "role", "resource_id": "org_acme:admin" } }, { "op": "create", "resource_type": "organization", "resource_id": "org_acme", "relation": "can_write_reports", "subject": { "resource_type": "role", "resource_id": "org_acme:admin" } } ]'
Now that we’ve associated a role with a set of organization permissions, let’s assign the role to a user. In the context of an application, this is typically done during the signup flow. It can also be part of an admin workflow where an admin user can update their team members’ role assignments.
curl "https://api.workos.com/fga/v1/warrants" \ -X POST \ -H "Authorization: Bearer sk_example_123456789" \ --data-raw \ '{ "op": "create", "resource_type": "role", "resource_id": "org_acme:admin", "relation": "member", "subject": { "resource_type": "user", "resource_id": "user_2oDKF2uOXwUqbnDgniHZsVBm5NP" } }'
Now that our access model and warrants are set up, we can add access checks in our app that will help us enforce our multi-tenant setup. For example, we might want to check if a particular user (user1) has the ability to edit a report (reportA). The answer to this query will depend on multiple factors including user1’s tenant and whether they are part of the ‘admin’ role which has the ability to edit reportA. We can query FGA for this check from our code as follows:
curl "https://api.workos.com/fga/v1/check" \ -X POST \ -H "Authorization: Bearer sk_example_123456789" \ --data-raw \ '{ "checks": [ { "resource_type": "organization", "resource_id": "org_acme", "relation": "can_read_reports", "subject": { "resource_type": "user", "resource_id": "user_2oDKF2uOXwUqbnDgniHZsVBm5NP" } } ] }'
curl "https://api.workos.com/fga/v1/query?q=select%20organization%20where%20user:user_2oDKF2uOXwUqbnDgniHZsVBm5NP%20is%20*" \ -X GET \ -H "Authorization: Bearer sk_example_123456789"
In this guide, we created a per-organization role based access control system for our B2B SaaS application using FGA. With this authorization model, our application can easily allow customers to create their own roles, assign organization-scoped permissions to those roles, and assign those roles to individual users in order to grant them permissions.