Enforcer

WorkOS FGA alternatives

WorkOS Fine-Grained Authorization is a hosted authorisation service that extends the company's existing RBAC with a resource hierarchy, so a role assigned on a parent resource grants its permissions on every child beneath it. It is not a Zanzibar tuple store, and WorkOS says so in its own migration guide: "OpenFGA uses relation-based access control (ReBAC) with explicit tuple storage, while WorkOS FGA uses hierarchical role-based access control (RBAC) with automatic permission inheritance." You define resource types, roles and permissions in the WorkOS Dashboard, and the docs state that resource types cannot be created, modified or deleted through the public API, with "Resource types per environment are limited to 50." Your application then registers each resource instance through the API, because "Resource instances are registered through the WorkOS API". Subjects are WorkOS organisation memberships or groups. If you use AuthKit, WorkOS is your identity provider and those memberships are already WorkOS records, so no identity is mirrored and the caller is already resolved. The standalone path is the one where you bring your own sign-in: "FGA works with any authentication system.", and there you manage users, organisations and memberships through the API yourself. This page sets out the reasons teams go looking for something else, the cases where staying put is the right call, and six alternatives.

Why teams look for a WorkOS FGA alternative

When to stay

Stay on WorkOS FGA if you already use WorkOS for SSO, Directory Sync or AuthKit and your model really is a tree of workspaces, projects and apps. The inheritance does genuine work: assign workspace-admin once and it grants the child permissions on every project and app beneath, with no per-object writes. Organisation-scoped roles and their permissions are embedded in AuthKit access tokens, so org-wide checks cost no network call. Enterprise onboarding is covered as well: "IdP role assignment lets you map identity provider groups to organization-scoped roles." The docs name Okta and Azure AD, and state the bound in the same place: "Today, IdP role assignment supports organization-scoped roles only." A group mapped to a role on one specific project is therefore not covered.

It also ships the read endpoints most homegrown systems never get round to. The access checks page documents four: check for "A single permission on a single resource. Best for action handlers and route gates."; listEffectivePermissions for "Many permissions on a single resource. Best for detail pages that render multiple permission-gated components."; listResourcesForMembership for "A single permission across many resources. Best for list views, navigation, and pickers."; and listMembershipsForResource for "All users who have a permission on a single resource. Best for share dialogs and member lists." Enforcer ships none of the four. The same page states "Sub-50ms response times (p95)" and strong consistency, so a role change takes effect immediately rather than after a replication lag. Listing users takes a direct or indirect switch, where "Direct assignments (default) return only users with an explicit role on this specific resource." and "Indirect assignments return all users with access, including through parent resources." Listing resources handles inheritance too: "This endpoint returns all child resources of a parent where the user has the specified permission, including through inheritance." One stated gap sits on that page, named without its bounds defined: "Indirect assignment lookups are not supported yet." One modelling constraint to plan around: "Today FGA supports two subject types: organization memberships (users) and groups." A non-human principal is represented by giving it an organisation membership. If you want one vendor for identity, directory sync and coarse-to-medium authorisation, and you do not need row-level or document-level decisions, this is a reasonable place to stop looking.

The options

OptionModelStrengthTrade-off
Enforcer (Instruxi)Identity and authorisation in one service. It signs users in with email OTP, passkeys, SIWE or enterprise SSO and holds tenants, roles and groups, so a policy is handed an already-resolved caller. POST /authz/check returns success, allow and a reason, along with the id of the policy that produced the decision. Custom policies are Rego in package enforcer.custom, compiled at write time, capped at 64KB with a 100ms evaluation budget, and creating one requires tenant admin.With AuthKit, WorkOS resolves the caller itself, so that alone is not a difference. What differs is what you keep in sync and what comes back. Enforcer holds no copy of your resource tree, so there is nothing to update on create, rename, reparent and delete. A named Rego policy can express the conditions, intersections and negations that the WorkOS migration guide tells you to write in application code instead. And the decision carries a reason and the policy id, where the documented WorkOS check response is {"authorized": true}.List filtering only over Enforcer’s own resource types. The engine compiles the same policy into a SQL WHERE clause by partial evaluation, exposed over gRPC as CompileFilter, but only for the eleven types registered in Enforcer’s own process (account, group, tenant, ticket, wallet and six more). There is no REST endpoint for it and no way to register your own tables, so you cannot use it to filter your application’s data. It cannot do either over your tables, and it has no equivalent of the four WorkOS access check endpoints. On POST /authz/check the resource is described by the caller and never looked up, so owner_id is an assertion your application must resolve server side before it calls. There is no shadow or preview evaluation: an inactive policy is stored and simply not evaluated. There is no decision log out of the box, because GET /audit-events is the identity and admin trail and never records authorisation decisions, so you log the returned reason and policy id yourself. It is also small and new, with no independent reviews.
OpenFGAOpen source Zanzibar-inspired engine. Its own site describes it as "an open-source authorization solution that allows developers to build granular access control using an easy-to-read modeling language and friendly APIs." and states "We are a Cloud Native Computing Foundation incubating project." A schema DSL defines types and relations, and access is stored as explicit relationship tuples.Per-object permissions at high cardinality are the normal case rather than the exception, which is precisely the workload WorkOS asks you to keep in your own database. It ships reverse queries over the tuple store, and self-hosting means no per-check vendor bill and no identity data leaving your infrastructure.You operate it. There is no bundled identity, directory sync or SSO, so the caller and the tuples are yours to keep correct, and the schema DSL is a real thing to learn before the first check works.
SpiceDB (AuthZed)Open source Zanzibar implementation with a relation schema and tuples, run self-hosted or as AuthZed's managed service.Consistency is configured per API request. The docs state: "SpiceDB's solution is to leverage the v1 API's ability to specify the desired consistency level on a per-request basis by using ZedTokens." The documented modes are minimize_latency, at_least_as_fresh, at_exact_snapshot and fully_consistent. That control matters when a stale allow is a security problem rather than an inconvenience. It also answers reverse queries, which Enforcer cannot.It is an authorisation engine only, with the same operational and modelling burden as OpenFGA, and per-request consistency is a correctness decision you now own at every call site. WorkOS takes that decision away by offering strong consistency as the default.
Oso CloudA hosted authorisation service with its own policy language, Polar. The docs state that "Polar can express any model, including RBAC, ReBAC, ABAC, fine-grained authorization, as well as patterns like organizational hierarchies, custom roles, and more."One policy language covers roles, relationships and attribute conditions together, so a hierarchy plus a condition on the request does not need a second system bolted on the side. List filtering is part of the product: "Oso Cloud evaluates the request against your authorization logic and facts, returning a boolean, list, or logic to execute against your database."Polar is a proprietary language with a smaller talent pool than Rego or plain YAML, and the evaluation path depends on a single vendor's hosted service.
CerbosStateless open source Policy Decision Point. The README says: "It enables you to define powerful, context-aware access control rules for your application resources in simple, intuitive YAML policies; managed and deployed via your Git-ops infrastructure." The PDP runs self-hosted, and Cerbos Hub is the optional hosted control plane.Policies are files in your repository, so they review, test and deploy like code. The PDP holds no data, which makes it straightforward to run next to the application and removes the data residency argument entirely.Because the PDP is stateless, every check has to be handed the principal and resource attributes by the caller, and relationship-heavy models such as nested sharing are awkward compared with a tuple store.
Auth0 Fine-Grained AuthorizationHosted Zanzibar-style service published by Okta under the Auth0 brand. Its docs describe it as "Auth0 Fine-Grained Authorization enables developers to design authorization models, from coarse grained to fine-grained, in a way that's centralized, flexible, fast, scalable, and easy to use." You define an authorisation model, write relationship tuples, then call check.Managed Zanzibar semantics with per-object tuples, sitting next to an identity platform many teams already run, with modelling guides covering user groups, roles and permissions, and relationships between objects.The same burden as every tuple store: the relationship data is a second source of truth your application has to write on every create, share, move and delete, and getting the model right up front is most of the work.

Where Enforcer fits, and where it does not

Enforcer differs from WorkOS FGA in a narrow way, and is worse in several that will matter more to most teams than the differences do.

What actually differs. If you use AuthKit, WorkOS is already your identity provider, an FGA subject is a WorkOS organisation membership, and the caller is resolved inside WorkOS. Enforcer is not different there, and any claim that it is would be wrong. Three things do differ. You mirror your resource tree into WorkOS and keep it in sync on create, rename, reparent and delete, where Enforcer keeps no copy of it. Custom policies in Enforcer are Rego, so conditions, intersections and negations live in the policy rather than in the code around the check, which is the class of logic the WorkOS migration guide tells you to handle in your application. And an Enforcer decision comes back with a reason and the id of the policy that produced it, where the documented WorkOS check response is {"authorized": true}.

Where Enforcer is worse, plainly. List filtering only over Enforcer’s own resource types. The engine compiles the same policy into a SQL WHERE clause by partial evaluation, exposed over gRPC as CompileFilter, but only for the eleven types registered in Enforcer’s own process (account, group, tenant, ticket, wallet and six more). There is no REST endpoint for it and no way to register your own tables, so you cannot use it to filter your application’s data. It cannot do either over your own tables. WorkOS documents four access check endpoints and Enforcer ships none of them, so member lists, sharing dialogs, detail pages that gate several components, and access reviews are your problem to build. On POST /authz/check the resource is described by the caller and never looked up, so owner_id is an assertion the application must resolve server side before it calls. There is no shadow or preview evaluation: an inactive policy is stored and simply not evaluated, so you cannot dry-run a change against real traffic. There is no decision log out of the box, because GET /audit-events is the identity and admin trail, covering logins, API keys, roles and tenants, and never records authorisation decisions. You log the returned reason and policy id yourself. WorkOS also publishes "Sub-50ms response times (p95)" and strong consistency for access checks; Enforcer publishes no latency figures.

One thing to understand before you ship. If you send no policy_id, a built-in baseline runs, and it is not blanket default-deny. It already allows any action other than manage on a row whose owner_id is the caller, before you have written a single rule, along with group and group type reads in your tenant, your own audit events and global policies; an admin with cross_tenant gets everything. A named policy replaces that decision rather than intersecting with it, and the contexts field only reaches a named policy, so arguments you pass are invisible to the baseline. Custom policies are Rego in package enforcer.custom, compiled at write time, capped at 64KB with a 100ms evaluation budget, and creating one requires tenant admin.

And the obvious one. Enforcer is small and new next to WorkOS, Auth0 and Keycloak. There are no independent reviews and no large community to search when something breaks. If your model is a clean tree of workspaces and projects and you already buy WorkOS for SSO, staying put is the cheaper answer.

Sources checked 15 September 2026: workos.com/docs/fga · workos.com/docs/fga/resource-types · workos.com/docs/fga/resources · workos.com/docs/fga/access-checks · workos.com/docs/fga/resource-discovery · workos.com/docs/fga/high-cardinality-entities · workos.com/docs/fga/standalone-integration · workos.com/docs/fga/idp-role-assignment · workos.com/docs/fga/migration-openfga · github.com/workos/workos-node/releases/tag/v9.0. · openfga.dev · authzed.com/docs/spicedb/concepts/consistency

Enforcer is identity and authorization in one system, for applications and AI agents. Docs at docs.instruxi.dev. Related: Authorization tools compared · Why was this request denied? · OpenFGA alternatives · Cerbos alternatives · Permit.io alternatives