workos-rbac
npx skills add https://github.com/workos/skills --skill workos-rbac
Agent 安装分布
Skill 文档
WorkOS Role-Based Access Control
Step 1: Fetch Documentation (BLOCKING)
STOP. Do not proceed until complete.
WebFetch these URLs in order:
https://workos.com/docs/rbac/quick-starthttps://workos.com/docs/rbac/organization-roleshttps://workos.com/docs/rbac/integrationhttps://workos.com/docs/rbac/indexhttps://workos.com/docs/rbac/idp-role-assignmenthttps://workos.com/docs/rbac/configuration
These docs are the source of truth. If this skill conflicts with them, follow the docs.
Step 2: Pre-Flight Validation
Environment Variables
Check .env or .env.local for:
WORKOS_API_KEY– starts withsk_WORKOS_CLIENT_ID– starts withclient_(if using AuthKit integration)
Verify:
grep -E "WORKOS_API_KEY|WORKOS_CLIENT_ID" .env* 2>/dev/null || echo "FAIL: Environment variables missing"
SDK Presence
Confirm WorkOS SDK is installed:
# For Node.js projects
npm list @workos-inc/node 2>/dev/null || echo "WARN: @workos-inc/node not found"
# For other SDKs, check package manager's list command
Critical: Do not write code using SDK methods until SDK is confirmed installed.
Step 3: Configuration Strategy (Decision Tree)
Authorization model?
|
+-- Environment-level roles only
| |
| +-- Same roles for all orgs --> Configure in WorkOS Dashboard > Roles
|
+-- Organization-specific roles
|
+-- Different roles per org --> Configure per-org in Dashboard > Organizations > [Org] > Roles
Key distinction:
- Environment roles: Global across all organizations, slug format
role-slug - Organization roles: Per-org custom roles, slug format
org:role-slug(prefix automatic)
Critical: Organization roles override environment roles. If an org has ANY custom roles, it gets its own default role and priority order.
Step 4: Dashboard Configuration
For Environment-Level Roles
Navigate to WorkOS Dashboard > Roles:
- Click “Create role”
- Define role slug (e.g.,
admin,member,viewer) - Assign permissions to role
- Set default role for new organization memberships
- Configure priority order (highest to lowest privilege)
Verify:
# API check for configured roles (requires jq)
curl -s -u "$WORKOS_API_KEY:" https://api.workos.com/roles \
| jq -r '.data[].slug' \
|| echo "FAIL: Cannot fetch roles - check API key"
For Organization-Specific Roles
Navigate to WorkOS Dashboard > Organizations > [Select Organization] > Roles:
- Click “Create role” on organization’s Roles tab
- Define role slug (system adds
org:prefix automatically) - Assign permissions
- Note: First custom role triggers org-specific default role and priority order
Critical: Once an org has custom roles, new environment roles are still available to it but placed at BOTTOM of priority order.
Step 5: Permission Model Setup
Define Permissions
Permissions follow resource:action format (e.g., video:create, settings:manage).
Best practice pattern:
Resource categories:
- Core features: video:create, video:view, video:delete
- Management: user:manage, settings:manage
- Reporting: analytics:view
Navigate to Dashboard > Roles > [Select Role] > Permissions:
- Create permissions with resource:action format
- Assign to appropriate roles
- Test with lowest-privilege role first
Permission Granularity Decision
How granular should permissions be?
|
+-- Resource-level (video:*)
| |
| +-- Simple apps, few roles --> video:view, video:manage
|
+-- Action-level (video:create, video:delete)
|
+-- Complex apps, many roles --> Fine-grained control per action
Step 6: Role Assignment Integration (Decision Tree)
How are users getting roles?
|
+-- Manual via API
| |
| +-- Use Organization Membership API --> Step 6A
|
+-- Via Identity Provider
| |
| +-- SSO group mapping --> Step 6B
| +-- Directory Sync group mapping --> Step 6C
|
+-- Via AuthKit JIT provisioning
|
+-- Configure provisioning rules --> See workos-authkit-base skill
Step 6A: Manual Role Assignment via API
Use Organization Membership API to assign/update roles:
# Example: Update organization membership role
curl -X PUT "https://api.workos.com/user_management/organization_memberships/{id}" \
-u "$WORKOS_API_KEY:" \
-H "Content-Type: application/json" \
-d '{
"role_slug": "admin"
}'
For multiple roles:
# Assign multiple roles (if multiple roles feature enabled)
curl -X PUT "https://api.workos.com/user_management/organization_memberships/{id}" \
-u "$WORKOS_API_KEY:" \
-H "Content-Type: application/json" \
-d '{
"role_slugs": ["admin", "billing-manager"]
}'
Verify assignment:
# Check membership roles
curl -s -u "$WORKOS_API_KEY:" \
"https://api.workos.com/user_management/organization_memberships/{id}" \
| jq -r '.role.slug' \
|| echo "FAIL: Cannot fetch membership"
Step 6B: SSO Group Role Assignment
Navigate to Dashboard > SSO > [Connection] > Advanced:
- Enable “Group role assignment”
- Map IdP groups to WorkOS roles:
- IdP group:
Engineeringâ WorkOS role:developer - IdP group:
Adminsâ WorkOS role:admin
- IdP group:
Behavior: Role updates on EVERY authentication. Takes precedence over API/Dashboard assignments.
Reference: https://workos.com/docs/rbac/idp-role-assignment
Step 6C: Directory Sync Group Role Assignment
Navigate to Dashboard > Directory Sync > [Connection] > Groups:
- Enable “Group role assignment”
- Map directory groups to roles
- Enable directory provisioning in AuthKit if using automatic org membership creation
Behavior: Role updates on directory events (user added/removed from group). Takes precedence over API/Dashboard assignments.
Reference: https://workos.com/docs/rbac/idp-role-assignment
Step 7: Access Control Implementation
Reading Roles from Sessions (AuthKit Integration)
For server-side components:
import { getUser } from '@workos-inc/authkit-nextjs';
const { user } = await getUser();
// Single role
const role = user?.role?.slug;
// Multiple roles (if enabled)
const roles = user?.roles?.map(r => r.slug) || [];
For client-side components:
'use client';
import { useAuth } from '@workos-inc/authkit-nextjs';
function ProtectedComponent() {
const { user } = useAuth();
const role = user?.role?.slug;
if (role !== 'admin') {
return <div>Access denied</div>;
}
return <div>Admin content</div>;
}
Checking Permissions
Pattern 1: Role-based checks (simple)
const isAdmin = user?.role?.slug === 'admin';
const canManageSettings = ['admin', 'owner'].includes(user?.role?.slug);
Pattern 2: Permission-based checks (recommended)
// Check if user has specific permission
const permissions = user?.role?.permissions || [];
const canCreateVideo = permissions.some(p =>
p.resource === 'video' && p.action === 'create'
);
// Or use wildcard pattern if permissions include wildcards
const canManageUsers = permissions.some(p =>
(p.resource === 'user' && p.action === 'manage') ||
(p.resource === 'user' && p.action === '*') ||
(p.resource === '*' && p.action === '*')
);
Pattern 3: Helper function (reusable)
function hasPermission(
user: User | null,
resource: string,
action: string
): boolean {
if (!user?.role?.permissions) return false;
return user.role.permissions.some(p =>
(p.resource === resource || p.resource === '*') &&
(p.action === action || p.action === '*')
);
}
// Usage
if (hasPermission(user, 'video', 'delete')) {
// Show delete button
}
Multiple Roles Pattern
If multiple roles are enabled, aggregate permissions:
function hasPermissionMultiRole(
user: User | null,
resource: string,
action: string
): boolean {
if (!user?.roles?.length) return false;
// Check if ANY role grants the permission
return user.roles.some(role =>
role.permissions?.some(p =>
(p.resource === resource || p.resource === '*') &&
(p.action === action || p.action === '*')
)
);
}
Step 8: API Route Protection
Middleware Pattern (Recommended)
// middleware.ts or lib/auth.ts
import { getUser } from '@workos-inc/authkit-nextjs';
import { NextResponse } from 'next/server';
export async function requireRole(
allowedRoles: string[]
): Promise<User | NextResponse> {
const { user } = await getUser();
if (!user) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
const userRole = user.role?.slug;
if (!userRole || !allowedRoles.includes(userRole)) {
return NextResponse.json(
{ error: 'Forbidden' },
{ status: 403 }
);
}
return user;
}
// Usage in API route
export async function DELETE(request: Request) {
const result = await requireRole(['admin', 'owner']);
if (result instanceof NextResponse) return result;
const user = result;
// Proceed with admin logic
}
Permission-Based Protection
export async function requirePermission(
resource: string,
action: string
): Promise<User | NextResponse> {
const { user } = await getUser();
if (!user) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
const hasPermission = user.role?.permissions?.some(p =>
(p.resource === resource || p.resource === '*') &&
(p.action === action || p.action === '*')
);
if (!hasPermission) {
return NextResponse.json(
{ error: 'Forbidden - missing permission' },
{ status: 403 }
);
}
return user;
}
// Usage
export async function POST(request: Request) {
const result = await requirePermission('video', 'create');
if (result instanceof NextResponse) return result;
// Proceed with video creation
}
Step 9: Organization Role Management
When to Use Organization Roles
Does this organization need custom roles?
|
+-- No --> Use environment roles (default)
|
+-- Yes
|
+-- Why?
|
+-- Stricter permissions than standard --> Create org role with subset of permissions
|
+-- Different role names/structure --> Create org-specific roles
|
+-- Compliance requirements --> Create custom roles per org's policy
Creating Organization Roles
Via Dashboard:
- Navigate to Organizations > [Org] > Roles
- Click “Create role”
- Define slug (e.g.,
limited-admin) - System automatically prefixes with
org:â final slug:org:limited-admin - Assign permissions subset
Impact of first org role:
- Organization gets its own default role setting
- Organization gets its own priority order
- Environment roles still available but at bottom of priority
Critical: If you delete an environment role that’s the default for orgs, you MUST select a replacement default for affected orgs.
Verification Checklist (ALL MUST PASS)
Run these commands to confirm integration:
# 1. Check environment variables exist
grep -q "WORKOS_API_KEY=sk_" .env* && echo "PASS: API key found" || echo "FAIL: API key missing or invalid format"
# 2. Check roles are configured (requires API key)
curl -s -u "$WORKOS_API_KEY:" https://api.workos.com/roles \
| jq -r '.data | length' \
| grep -q -v '^0$' \
&& echo "PASS: Roles configured" \
|| echo "FAIL: No roles found"
# 3. Check SDK is installed
npm list @workos-inc/node 2>/dev/null | grep -q "@workos-inc/node" \
&& echo "PASS: SDK installed" \
|| echo "WARN: SDK not found (may be using different SDK)"
# 4. Check for access control implementation
grep -r "role\.slug\|permissions\.some\|hasPermission" app/ src/ \
| head -n 1 \
| grep -q "." \
&& echo "PASS: Access control checks found" \
|| echo "FAIL: No access control implementation detected"
# 5. Build succeeds
npm run build 2>&1 | tail -n 5
All checks must pass before considering integration complete.
Error Recovery
“Forbidden – missing permission” (403)
Root cause: User’s role lacks required permission for action.
Fix decision tree:
Is this the correct behavior?
|
+-- Yes (user shouldn't have access) --> Update UI to hide unauthorized actions
|
+-- No (user should have access)
|
+-- Check user's role in Dashboard
| |
| +-- Role correct? --> Add missing permission to role
| |
| +-- Wrong role? --> Update organization membership role
|
+-- Using IdP assignment? --> Check group mappings in SSO/Directory Sync settings
“Unauthorized” (401)
Root cause: User not authenticated or session expired.
Fix:
- Check AuthKit integration is working:
curl localhost:3000/api/auth/session - Verify middleware is calling
getUser()correctly - Check auth cookies are being sent with requests
- For API routes, confirm Authorization header or cookie forwarding
Role assignment not updating
Symptom: User assigned new role but session shows old role.
Root cause: Session not refreshed after role change.
Fix decision tree:
Assignment method?
|
+-- API/Dashboard manual assignment
| |
| +-- User must log out and log back in --> Session update needed
|
+-- IdP (SSO) assignment
| |
| +-- Updates on next authentication --> Force re-auth or wait for next login
|
+-- Directory Sync assignment
|
+-- Updates on directory events --> Check event delivery in Dashboard > Events
Workaround: Force session refresh by having user log out and back in.
Organization role slug conflicts
Symptom: Cannot create org role with same slug as environment role.
Expected behavior: This is prevented by automatic org: prefix. Org role admin becomes org:admin, environment role is admin.
If seeing conflict: Check if slug already has org: prefix manually added (don’t add it yourself).
Multiple roles not working
Root cause: Multiple roles feature not enabled for environment.
Check: WebFetch https://workos.com/docs/rbac/integration to confirm feature availability and enablement process.
Symptom: API rejects role_slugs array, only accepts role_slug string.
Fix: Contact WorkOS support to enable multiple roles feature for your environment.
Permission wildcards not matching
Symptom: Permission check fails despite wildcard permission (video:* or *:*).
Root cause: Permission matching logic doesn’t handle wildcards.
Fix by updating permission check helper:
function matchesPermission(
granted: { resource: string; action: string },
required: { resource: string; action: string }
): boolean {
const resourceMatch =
granted.resource === '*' ||
granted.resource === required.resource;
const actionMatch =
granted.action === '*' ||
granted.action === required.action;
return resourceMatch && actionMatch;
}
“Role not found” when assigning
Root cause: Role slug doesn’t exist in environment or organization.
Check:
# List all roles for debugging
curl -s -u "$WORKOS_API_KEY:" https://api.workos.com/roles \
| jq -r '.data[] | "\(.slug) - \(.name)"'
# For org-specific roles, check organization's roles in Dashboard
Common mistakes:
- Using
org:role-slugfor environment role (wrong prefix) - Using
role-slugfor org role (missing prefix) - Typo in slug (roles are case-sensitive)
Default role not applied to new members
Root cause: Default role configuration missing or incorrect.
Check: Dashboard > Roles > Default role setting (or per-org if using org roles).
Verify:
# Create test membership and check assigned role
curl -X POST "https://api.workos.com/user_management/organization_memberships" \
-u "$WORKOS_API_KEY:" \
-H "Content-Type: application/json" \
-d '{
"user_id": "user_test_123",
"organization_id": "org_test_123"
}' \
| jq -r '.role.slug'
Should return default role slug, not null.
Related Skills
- workos-authkit-nextjs: For reading roles from AuthKit sessions
- workos-fga: For fine-grained authorization beyond role-based checks
- workos-sso: For IdP group role assignment via SSO
- workos-directory-sync: For IdP group role assignment via Directory Sync
- workos-api-organization: For managing organizations with custom roles