Use policies to implement advanced attribute based access control with FGA, providing relevant data from your application at access control check time.
WorkOS FGA allows you to define custom logic that is executed when evaluating access checks. A policy is a boolean expression that specifies additional conditions to be satisfied in order for an access check to be authorized. Use policies to enforce complex rules and conditions that go beyond simple role-based access control (RBAC) or attribute-based access control (ABAC).
Policies can be defined on warrants or as part of your schema.
FGA currently supports defining policy expressions using expr. Support for more policy languages will be coming soon.
You can optionally include a policy in a warrant. For a warrant to match a check/query, its policy must evaluate to true. The system evaluates policies after matching the warrant based on its resource, relation, and subject attributes. It evaluates the policy in the context of the check/query request, using any dynamic values provided via the context attribute (see context below) to process the expression.
For example, the following warrant states that [role:accountant] is a [member] of [permission:view-profits-and-losses]
only when companyId == 'wayne-enterprises'
:
{ "resource_type": "permission", "resource_id": "view-profits-and-losses", "relation": "member", "subject": { "resource_type": "role", "resource_id": "accountant" }, "policy": "companyId == 'wayne-enterprises'" }
Policies can reference dynamic variables. You must provide values for these variables in check or query requests via the context attribute (e.g., role, tenant, or geographic location). Before evaluating a policy, the system substitutes the provided values into the expression. Policy expressions undergo static type checking, so type mismatches prevent evaluation from returning true. Policies with missing values or evaluation errors also do not return true. The system compiles and statically checks policies for errors upon creation.
Policies have numerous uses but are most commonly used to implement forms of attribute-based access control (ABAC). For example, create a warrant that only matches users visiting from a specific IP address:
{ "resource_type": "database", "resource_id": "prod", "relation": "admin", "subject": { "resource_type": "user", "resource_id": "ops-user" }, "policy": "user.client_ip == '192.168.1.1'" }
Combine policies with role-based access control (RBAC) to support different role/permission mappings per customer or tenant. For example, define a warrant stating that [role:accountant]
grants [permission:view-balance-sheet]
only when companyId == 'wayne-enterprises'
:
{ "resource_type": "permission", "resource_id": "view-balance-sheet", "relation": "member", "subject": { "resource_type": "role", "resource_id": "accountant" }, "policy": "companyId == 'wayne-enterprises'" }
Create another warrant specifying that [role:accountant]
grants users [permission:view-profits-and-losses]
only when companyId == 'daily-planet'
:
{ "resource_type": "permission", "resource_id": "view-profits-and-losses", "relation": "member", "subject": { "resource_type": "role", "resource_id": "accountant" }, "policy": "companyId == 'daily-planet'" }
Make access checks, passing in different companyId
values via the context
based on the company a user belongs to:
{ "checks": [ { "resource_type": "permission", "resource_id": "view-profits-and-losses", "relation": "member", "subject": { "resource_type": "role", "resource_id": "accountant" }, "context": { "companyId": "wayne-enterprises" } } ] }
This access check returns false
because [role:accountant]
only grants [permission:view-profits-and-losses]
within the context of company daily-planet
.
You can reference policies in your schema, allowing you to define and reuse complex rules across different relations and inheritance rules. Use policies in the inherit clause of your schema definition. For example, consider the following schema:
version 0.3 type user type organization relation viewer [user] relation view [] inherit view if all_of policy ip_allowed relation viewer policy ip_allowed(clientIp string) { clientIp matches "192\\.168\\..*\\..*" }
Here, the view
relation is inherited based on the ip_allowed policy and the viewer relation. This means that users must meet the conditions of the ip_allowed
policy and also be in the viewer
relation to access the view
permission.
view
has no allowed types, so it cannot be assigned a warrant directly. Instead, it inherits from the viewer
relation and the ip_allowed
policy so that we can check if a user is in the viewer
relation and also meets the conditions of the ip_allowed
policy.
Make sure schema version is set to 0.3
or higher to use policies in your schema.
Policies can be defined in the Schema Editor of the WorkOS FGA Dashboard or via the API. Each policy consists of the following fields:
expr
is supportedExpr
policies are defined directly in your FGA schema, making it easy to view policies alongside your inheritance rules. More languages will be supported in the future and managed through a different user interface.
policy <policy_name>(<parameter_name> <parameter_type>, ...) { <expression> }
View expr
language documentation.
Policies are referenced in inheritance rules using their name.
inherit <relation_name> if policy <policy_name>
You can use policies to implement pure attribute-based access control (ABAC) without any inheritance rules or warrant data. This allows you to define access control based solely on user attributes or other context values as you would with a policy engine. For example:
version 0.3 type user type organization relation view_internal_settings [] inherit view_internal_settings if policy staff_user policy staff_user(user map) { user.email endsWith "@internal-domain.com" && user.role == "staff" }
In this example, the view_internal_settings
relation inherits from the staff_user
policy. This means only users with a staff role and an email ending in @internal-domain.com
can access view_internal_settings
.
Since this policy is the sole inheritance rule for view_internal_settings
, FGA does not check for warrants when evaluating access. This allows you to use FGA purely as an attribute-based access control (ABAC) system if desired.
Policies can also be combined with ReBAC inheritance rules to create more complex access control models. Consider the following example:
version 0.3 type user type organization relation admin [user] relation configure_payments [user] inherit configure_payments if all_of relation admin policy has_strong_auth policy has_strong_auth(user_attributes map) { user_attributes.mfa_enabled == true && user_attributes.account_age_days > 30 }
In this example, the configure_payments
relation inherits from both the admin
relation and the has_strong_auth
policy. This means that users must be an admin and meet the strong authentication requirements to access the configure_payments
relation.
WorkOS FGA provides API endpoints if you prefer managing policies programmatically and separately from the schema.
For more details, see the Policies API documentation.
Additional Dashboard and WorkOS CLI tools for policy and schema management are coming soon.
When making an FGA check, pass the required context values as you would with warrant policies. FGA evaluates warrant and schema policies together during access checks.
{ "checks": [ { "resource_type": "organization", "resource_id": "acme-corp", "relation": "configure_payments", "subject": { "resource_type": "user", "resource_id": "123" }, "context": { "user_attributes": { "mfa_enabled": true, "account_age_days": 45 } } } ] }
Policies can also be defined in the schema JSON format. Here’s an example of how to define a policy in JSON:
{ "version": "0.3", "resource_types": { "user": {}, "organization": { "relations": { "view": { "policy": "ip_allowed" } } } }, "policies": { "ip_allowed": { "name": "ip_allowed", "language": "expr", "parameters": [ { "name": "clientIp", "type": "string" } ], "expression": "clientIp matches \"192\\\\.168\\\\..*\\\\..*\"" } } }
Combine policies with inheritance rules to create complex access control models. For example, define a policy that checks specific conditions and apply it across multiple relations or inheritance rules:
version 0.3 type user type staff_group relation member [user] type org_role relation member [user] type organization relation internal_admin [staff_group] relation viewer [user, org_role] relation view_feature_1 [] inherit view_feature_1 if any_of relation member on internal_admin [staff_group] all_of any_of relation viewer relation member on viewer [org_role] policy valid_enterprise_plan policy valid_enterprise_plan(payment_plan map) { payment_plan.is_active == true && payment_plan.tier == "enterprise" }
In this example:
view_feature_1
access can be inherited based on multiple conditions.staff_group
members) can access view_feature_1
.viewer
or an org_role
can also access it if they meet the valid_payment_plan
policy requirements.Using policies and inheritance rules together provides a powerful way to model permissions for:
To start using policies in your schema, ensure the following: