Skip to main content

Authentication & RBAC

Workloom uses Auth.js v5 (NextAuth) with JWT sessions. Role-based access control enforces what each user can do within an organization.

Authentication Providers​

ProviderAuto-provisionRole sync
OIDC (Zitadel, Okta, etc.)YesYes
GitHubNoNo
GoogleNoNo

OIDC is the primary provider for enterprise deployments. GitHub and Google are convenience providers — users must be manually added to organizations.

Role Hierarchy​

Roles are ordered by privilege (highest first):

RoleWhat they can do
ownerFull org control, billing, SSO configuration
adminManage members, all packages, all workspaces
maintainerPublish packages, approve versions, manage workspace assignments
developerPublish packages, edit workspaces
viewerRead-only access

A user's effective role is the highest privilege across all resolution sources.

Role Mapping​

Most organizations have IdP groups that don't directly match Workloom's five roles. The role mapping system translates external groups to internal roles.

Environment Variable Mapping​

Set default mappings via environment variables:

ROLE_MAP_ADMIN=platform-admin,infra-admin,dev-lead-*
ROLE_MAP_DEVELOPER=backend-eng,frontend-eng,platform-eng
ROLE_MAP_VIEWER=contractor-*,intern-*

Patterns support * glob matching — dev-* matches dev-frontend, dev-backend, etc.

Database Mapping (Per-Org)​

Organizations can override the defaults via the role_mappings table. Database entries take precedence over environment variables.

This allows different organizations to map the same IdP group to different internal roles.

Provider Plugin System​

Role resolution uses a plugin architecture. Each provider implements a simple interface:

interface RoleProvider {
name: string;
enabled(): boolean;
resolveRoles(context: RoleResolutionContext): Promise<Role[]>;
}

Built-in Providers​

  • OIDC — extracts groups from IdP token, runs through role mapping
  • DB — reads existing membership from the database

Merge-All Strategy​

When multiple providers are enabled, Workloom collects roles from all of them and picks the highest privilege:

OIDC provider returns: ['maintainer']
DB provider returns: ['admin']
Effective role: 'admin' (highest privilege wins)

Extending with Custom Providers​

Add new providers (LDAP, custom APIs) by implementing the RoleProvider interface and registering in the provider registry. Each provider manages its own caching — OIDC uses the token data, DB uses direct queries, LDAP could use Redis TTL.

How Login Works​

  1. User authenticates via OIDC provider
  2. IdP returns token with group claims
  3. Groups extracted from the configured claim (AUTH_OIDC_ROLE_CLAIM)
  4. Provider registry resolves effective role (mapping + merge-all)
  5. Role persisted to organization_memberships table
  6. Raw IdP groups stored in users.idp_groups for audit
  7. JWT cookie contains only identity (id, name, email, image) — no roles

Retrieving Roles​

Roles are not stored in the session cookie (prevents bloat with many IdP groups).

Server-side:

import { getUserRoleAction } from '@/server/get-user-role-action';
const { role, orgId } = await getUserRoleAction();

Client-side:

import { useUserRole } from '@/hooks/use-user-role';
const { role, orgId, loading } = useUserRole();

API routes use requireRole() which queries the database directly and throws 403 Forbidden if the user lacks the required role.

Configuration​

VariablePurpose
AUTH_OIDC_ROLE_CLAIMClaim name containing groups in the IdP token
AUTH_OIDC_DEFAULT_ROLEFallback when no mapping matches (default: developer)
AUTH_OIDC_AUTO_PROVISION_ORGOrg slug for automatic membership creation
ROLE_MAP_OWNERPatterns mapping to owner role
ROLE_MAP_ADMINPatterns mapping to admin role
ROLE_MAP_MAINTAINERPatterns mapping to maintainer role
ROLE_MAP_DEVELOPERPatterns mapping to developer role
ROLE_MAP_VIEWERPatterns mapping to viewer role