workos-fga
WorkOS Fine-Grained Authorization
Step 1: Fetch Documentation (BLOCKING)
STOP. Do not proceed until complete.
WebFetch: https://workos.com/docs/fga/index
The documentation is the source of truth. If this skill conflicts with docs, follow docs.
CRITICAL PRE-RELEASE NOTE: FGA is scheduled for Q1 2026 release. Check docs for current availability status. If endpoints return 404 or "not available", this feature is not yet released.
Step 2: Pre-Flight Validation
Check FGA Availability
# Test if FGA endpoints are live
curl -f -H "Authorization: Bearer ${WORKOS_API_KEY}" \
https://api.workos.com/authorization/resources 2>/dev/null && echo "FGA Available" || echo "FGA Not Yet Released"
If command fails: FGA is not available yet. Stop here.
Environment Variables
Check environment for:
WORKOS_API_KEY- starts withsk_WORKOS_CLIENT_ID- starts withclient_
# Verify keys exist and have correct prefixes
[[ $WORKOS_API_KEY == sk_* ]] && echo "API key valid" || echo "FAIL: Invalid API key"
[[ $WORKOS_CLIENT_ID == client_* ]] && echo "Client ID valid" || echo "FAIL: Invalid client ID"
SDK Verification
# Confirm WorkOS SDK is installed
npm list @workos-inc/node 2>/dev/null || echo "FAIL: SDK not installed"
If SDK missing: Install with npm install @workos-inc/node before continuing.
Step 3: Resource Type Design (CRITICAL PLANNING PHASE)
STOP. Design your resource hierarchy before writing code.
FGA extends RBAC with resource instances. You must define:
- Resource types - categories of objects (workspace, project, app)
- Hierarchy - parent-child relationships
- Permissions - which roles can do what on each type
Example Hierarchy
Organization (always root)
|
+-- Workspace
|
+-- Project
|
+-- App
Configure in Dashboard
- Navigate to WorkOS Dashboard → Authorization → Resource Types
- Create each resource type with:
slug- machine name (workspace, project, app)name- human-readable labelparent_type- optional, for inheritance
- Add permissions for each type
Verify dashboard config complete before Step 4.
Step 4: Resource Instance Registration
When users create resources in your app, register them with FGA.
Decision Tree: When to Register
User creates object in your app?
|
+-- Is it authorization-relevant? (workspace, project, NOT log entry)
| |
| +-- YES --> Register resource instance
| |
| +-- NO --> Do not register
|
+-- Is parent resource known?
|
+-- YES --> Include parent_resource_id in registration
|
+-- NO --> Register at org level (parent_resource_id = org_id)
Registration Pattern
// After creating resource in your database
const resource = await workos.fga.resources.create({
resource_type: 'workspace', // matches dashboard slug
external_id: dbWorkspace.id, // YOUR database ID
name: dbWorkspace.name,
parent_resource_id: orgId, // or parent workspace ID
});
// Store resource.id in your database alongside dbWorkspace.id
// You'll need both IDs for future operations
CRITICAL: Store both external_id (your ID) and resource.id (WorkOS ID) in your database. You need WorkOS ID for assignments.
Bulk Registration Pattern
If migrating existing resources:
// Fetch existing resources from your DB
const workspaces = await db.workspace.findMany();
// Register each with FGA
for (const ws of workspaces) {
const resource = await workos.fga.resources.create({
resource_type: 'workspace',
external_id: ws.id,
name: ws.name,
parent_resource_id: ws.orgId,
});
// Update your DB with WorkOS resource ID
await db.workspace.update({
where: { id: ws.id },
data: { workosResourceId: resource.id },
});
}
Step 5: Role Assignments
Assign roles to users for specific resources.
Assignment Pattern
// When adding user to workspace with role
await workos.fga.assignments.create({
organization_membership_id: membership.id, // from AuthKit
role_slug: 'workspace-admin', // role defined in dashboard
resource_id: workspace.workosResourceId, // from Step 4
});
Inheritance Behavior
IMPORTANT: If role has child-type permissions, FGA automatically propagates to children.
Example:
- User assigned
workspace-adminonWorkspace:finance - Role includes permission
project:edit - User automatically gets
project:editon ALL projects underWorkspace:finance
Do not manually assign roles to child resources if parent assignment covers it.
Remove Assignment
// When removing user from workspace
await workos.fga.assignments.delete({
organization_membership_id: membership.id,
role_slug: 'workspace-admin',
resource_id: workspace.workosResourceId,
});
Step 6: Access Checks
Check if user has permission before allowing action.
Single Permission Check
// Before allowing project edit
const canEdit = await workos.fga.check({
organization_membership_id: membership.id,
permission: 'project:edit',
resource_id: project.workosResourceId,
});
if (!canEdit.authorized) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
// Proceed with edit
Batch Check Pattern
// Check multiple permissions at once
const [canEdit, canDelete, canShare] = await Promise.all([
workos.fga.check({
organization_membership_id: membership.id,
permission: 'project:edit',
resource_id: project.workosResourceId,
}),
workos.fga.check({
organization_membership_id: membership.id,
permission: 'project:delete',
resource_id: project.workosResourceId,
}),
workos.fga.check({
organization_membership_id: membership.id,
permission: 'project:share',
resource_id: project.workosResourceId,
}),
]);
Step 7: Resource Discovery
Query which resources user can access.
Pattern: List User's Resources
// "Show all projects this user can edit"
const editableProjects = await workos.fga.organizationMemberships.resources({
organization_membership_id: membership.id,
resource_type: 'project',
permission: 'project:edit',
});
// Returns array of resource objects user can edit
Pattern: List Resource Members
// "Who has access to this workspace?"
const members =
await workos.fga.resources.organizationMemberships({
resource_id: workspace.workosResourceId,
});
// Returns array of memberships with access (direct or inherited)
Pattern: List User's Roles
// "What roles does this user have?"
const roles = await workos.fga.organizationMemberships.roles({
organization_membership_id: membership.id,
});
// Returns array of role assignments across all resources
Step 8: External ID Lookups
Use your database IDs directly without storing WorkOS IDs.
Pattern: Fetch by External ID
// If you only stored external_id, not workos resource_id
const resource = await workos.fga.organizations.resources.getByExternalId({
organization_id: orgId,
resource_type: 'workspace',
external_id: dbWorkspace.id, // YOUR database ID
});
// Now you have resource.id for assignments/checks
Trade-off: This adds extra API call. Storing both IDs (Step 4) is more efficient.
Verification Checklist (ALL MUST PASS)
# 1. Check FGA is available (not pre-release)
curl -f -H "Authorization: Bearer ${WORKOS_API_KEY}" \
https://api.workos.com/authorization/resources >/dev/null 2>&1 && echo "PASS" || echo "FAIL: FGA not available"
# 2. Check resource types configured in dashboard (manual verification required)
echo "MANUAL: Verify resource types exist in WorkOS Dashboard → Authorization"
# 3. Check resource registration code exists
grep -r "fga.resources.create" . --include="*.ts" --include="*.js" && echo "PASS" || echo "FAIL: No resource registration"
# 4. Check access control implemented
grep -r "fga.check" . --include="*.ts" --include="*.js" && echo "PASS" || echo "FAIL: No access checks"
# 5. Application builds
npm run build && echo "PASS" || echo "FAIL: Build error"
If check #1 fails: FGA is not released yet. Stop implementation until Q1 2026.
If check #2 fails: Configure resource types in Dashboard before writing code. This is a blocking requirement.
Error Recovery
"FGA endpoints return 404"
Root cause: Feature not released yet (scheduled Q1 2026).
Fix: Check docs for release status. Do not implement until available.
"Resource type not found"
Root cause: Resource type slug in code doesn't match Dashboard config.
Fix:
- Check exact slug in Dashboard → Authorization → Resource Types
- Match slug exactly in
resource_typeparameter (case-sensitive)
"Assignment failed: invalid organization_membership_id"
Root cause: Using user ID instead of organization membership ID.
Fix: Get membership ID from AuthKit:
// WRONG
await workos.fga.assignments.create({
organization_membership_id: user.id, // user ID
});
// CORRECT
const membership = await workos.organizations.memberships.list({
organization_id: orgId,
user_id: user.id,
});
await workos.fga.assignments.create({
organization_membership_id: membership.data[0].id, // membership ID
});
"Permission check always returns false"
Root cause 1: Role doesn't include permission in Dashboard config.
Fix: Add permission to role in Dashboard → RBAC → Roles.
Root cause 2: No assignment exists for user on resource or ancestor.
Fix: Check assignments exist:
# List user's roles to verify assignment
curl -H "Authorization: Bearer ${WORKOS_API_KEY}" \
"https://api.workos.com/authorization/organization_memberships/${MEMBERSHIP_ID}/roles"
Root cause 3: Using wrong resource ID (your DB ID instead of WorkOS ID).
Fix: Use resource.id from registration response, not external_id.
"Cannot delete resource: has children"
Root cause: Attempting to delete parent resource while children still exist.
Fix: Delete children first, then parent:
// Delete all projects under workspace first
for (const project of projects) {
await workos.fga.resources.delete(project.workosResourceId);
}
// Then delete workspace
await workos.fga.resources.delete(workspace.workosResourceId);
"SDK method not found"
Root cause: SDK version too old or FGA not yet released.
Fix:
# Update SDK to latest
npm install @workos-inc/node@latest
# Verify version supports FGA
npm list @workos-inc/node
Check SDK changelog for FGA support version.
Related Skills
- workos-rbac: Organization-level role-based access control (prerequisite for FGA)
- workos-authkit-nextjs: User authentication and organization management (provides membership IDs)
More from workos/skills
workos
Use when the user asks for a WorkOS docs URL, term, or dashboard field (Sign-in endpoint, initiate_login_uri, Redirect URI, `WORKOS_*` env vars), or is implementing, debugging, or migrating WorkOS — AuthKit, SSO/SAML, Directory Sync, RBAC, FGA, MFA, Vault, Audit Logs, Admin Portal, Pipes (Connected Apps), Feature Flags, Radar (bot/fraud detection), webhooks, Custom Domains, or migrating from Auth0, Clerk, Cognito, Firebase, Supabase, Stytch, Descope, or Better Auth. Also triggers on @workos-inc/* imports.
599workos-widgets
Use when the user is implementing, embedding, or debugging a WorkOS Widget — specifically the User Management, User Profile, Admin Portal SSO Connection, or Admin Portal Domain Verification widgets. Handles the full stack — detecting the frontend (Next.js, React, React Router, TanStack Start, Vite, SvelteKit), generating access tokens via the backend SDK in use (Node, Python, Go, Ruby, PHP, Java, .NET), and wiring up the widget component correctly per the bundled OpenAPI spec. Also use when code imports from @workos-inc/widgets or the user pastes <UserManagement /> or <UserProfile /> JSX.
272workos-authkit-nextjs
Integrate WorkOS AuthKit with Next.js App Router (13+). Server-side rendering required.
64workos-authkit-base
Architectural reference for WorkOS AuthKit integrations. Fetch README first for implementation details.
42workos-authkit-react
Integrate WorkOS AuthKit with React single-page applications. Client-side only authentication. Use when the project is a React SPA without Next.js or React Router.
33workos-authkit-tanstack-start
Integrate WorkOS AuthKit with TanStack Start applications. Full-stack TypeScript with server functions. Use when project uses TanStack Start, @tanstack/start, or vinxi.
28