Skip to main content
Roles are the foundation of authorization in ZITADEL. They define what users can do within your applications and how administrative access is controlled across organizations and instances.

Understanding the Role Hierarchy

ZITADEL has three levels of roles, each serving a different purpose:

Instance Roles

Control access to the entire ZITADEL instance. Only relevant for self-hosted deployments where you manage the instance.
  • IAM_OWNER: Full instance administration
  • Manage all organizations
  • Configure instance-wide policies

Organization Roles

Control administrative access within a specific organization.

ORG_OWNER

Complete control over the organization, its users, projects, and settings. Can manage all aspects of the organization.

ORG_USER_MANAGER

Can create, modify, and delete users within the organization. Cannot manage projects or organization settings.

ORG_PROJECT_PERMISSION_MANAGER

Can manage project grants and user grants (role assignments). Controls who has access to which projects.

ORG_AUDITOR

Read-only access to view organization data, users, projects, and audit logs. Cannot make changes.

Project Roles

Custom roles you define to control access to your applications. These are the roles your application uses for authorization. Examples:
  • admin - Full access to the application
  • editor - Can create and modify content
  • viewer - Read-only access
  • reports:read - Can view reports
  • users:write - Can manage users

How Role Assignment Works

Roles are assigned to users through user grants.

The Relationship Model

User + Organization + Project + Roles = User Grant
A user grant connects:
  • Who: A specific user
  • Where: In which organization context
  • What: Which project they can access
  • How: With which project roles

Example

Alice works for Acme Corp (Organization A) and needs access to the Customer Portal (Project 1):
await userGrantService.addUserGrant({
  userId: "alice-user-id",
  organizationId: "acme-org-id",
  projectId: "customer-portal-project-id",
  roleKeys: ["admin", "reports:read"]
});
Now Alice has the admin and reports:read roles when accessing the Customer Portal.

Project Roles in Detail

Defining Roles

When you create a project, you define custom roles specific to that project:
// Add roles to your project
await projectService.addProjectRole({
  projectId: "customer-portal-project-id",
  roleKey: "admin",
  displayName: "Administrator",
  group: "Management"
});

await projectService.addProjectRole({
  projectId: "customer-portal-project-id",
  roleKey: "editor",
  displayName: "Content Editor",
  group: "Content"
});

await projectService.addProjectRole({
  projectId: "customer-portal-project-id",
  roleKey: "viewer",
  displayName: "Viewer",
  group: "General"
});

Role Naming Conventions

Use simple, descriptive names:
  • admin
  • editor
  • viewer
  • manager
Best for: Simple applications with few roles
Use resource:action format:
  • users:read
  • users:write
  • reports:read
  • invoices:admin
Best for: Applications with many resources and granular permissions
Use dot notation for hierarchies:
  • sales.manager
  • sales.rep
  • support.tier1
  • support.tier2
Best for: Applications with organizational structures

Role Groups

The group field helps organize roles in the UI:
roleKey: "users:write",
displayName: "User Manager",
group: "User Management"
This groups all user management-related roles together in the admin interface, making it easier to assign roles.

Accessing Roles in Your Application

When a user authenticates, their roles are available in several places.

In Tokens

If you enable projectRoleAssertion on the project, roles appear in tokens: ID Token:
{
  "iss": "https://your-domain.zitadel.cloud",
  "sub": "163840776835432705",
  "urn:zitadel:iam:org:project:69629026806489455:roles": {
    "admin": {
      "69629023906488334": "Acme Corporation"
    },
    "reports:read": {
      "69629023906488334": "Acme Corporation"
    }
  }
}
Access Token:
{
  "urn:zitadel:iam:org:project:69629026806489455:roles": {
    "admin": {
      "69629023906488334": "Acme Corporation"
    }
  }
}

Via Userinfo Endpoint

const response = await fetch('https://your-domain.zitadel.cloud/oidc/v1/userinfo', {
  headers: {
    'Authorization': `Bearer ${accessToken}`
  }
});

const userInfo = await response.json();
const roles = userInfo['urn:zitadel:iam:org:project:YOUR_PROJECT_ID:roles'];

Via Introspection

curl -X POST https://your-domain.zitadel.cloud/oauth/v2/introspect \
  -u "client_id:client_secret" \
  -d "token=access_token"

Implementing Authorization

In Your Application

Once you have the user’s roles from the token, implement authorization logic:
// Extract roles from token
function getUserRoles(token) {
  const rolesClaim = token[`urn:zitadel:iam:org:project:${PROJECT_ID}:roles`];
  return Object.keys(rolesClaim || {});
}

// Check if user has a role
function hasRole(token, role) {
  const roles = getUserRoles(token);
  return roles.includes(role);
}

// Check if user has any of the specified roles
function hasAnyRole(token, requiredRoles) {
  const userRoles = getUserRoles(token);
  return requiredRoles.some(role => userRoles.includes(role));
}

// Example usage in an API endpoint
app.get('/api/admin/users', (req, res) => {
  if (!hasRole(req.user, 'admin')) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  
  // Admin logic here
});

Middleware Example (Express.js)

function requireRole(role) {
  return (req, res, next) => {
    if (!hasRole(req.user, role)) {
      return res.status(403).json({ 
        error: 'Forbidden',
        message: `This action requires the '${role}' role`
      });
    }
    next();
  };
}

function requireAnyRole(...roles) {
  return (req, res, next) => {
    if (!hasAnyRole(req.user, roles)) {
      return res.status(403).json({ 
        error: 'Forbidden',
        message: `This action requires one of: ${roles.join(', ')}`
      });
    }
    next();
  };
}

// Usage
app.delete('/api/users/:id', 
  requireRole('admin'),
  deleteUserHandler
);

app.get('/api/reports',
  requireAnyRole('admin', 'reports:read'),
  getReportsHandler
);

Multi-Organization Role Assignments

A user can have different roles in the same project depending on which organization they’re acting in.

Example Scenario

Bob is a consultant who works with multiple clients:
// Bob has admin role in Acme Corp
await addUserGrant({
  userId: "bob-id",
  organizationId: "acme-org-id",
  projectId: "customer-portal-id",
  roleKeys: ["admin"]
});

// Bob has viewer role in Beta Inc
await addUserGrant({
  userId: "bob-id",
  organizationId: "beta-org-id",
  projectId: "customer-portal-id",
  roleKeys: ["viewer"]
});
When Bob logs in:
  • Via bob@acme.com → He has the admin role
  • Via bob@beta.com → He has the viewer role
The roles in the token show both:
{
  "urn:zitadel:iam:org:project:customer-portal-id:roles": {
    "admin": {
      "acme-org-id": "Acme Corporation"
    },
    "viewer": {
      "beta-org-id": "Beta Inc"
    }
  }
}

Authorization Checks in ZITADEL

ZITADEL can enforce authorization at the authentication level.

Authorization Required

Enable this on the project to ensure users have at least one role:
await projectService.updateProject({
  projectId: "customer-portal-id",
  authorizationRequired: true
});
Now:
  • Users with role assignments can log in ✓
  • Users without role assignments are denied ✗

Project Access Required

Ensure the user’s organization has access to the project:
await projectService.updateProject({
  projectId: "customer-portal-id",
  projectAccessRequired: true
});
Now:
  • Users from the owner organization can log in ✓
  • Users from granted organizations can log in ✓
  • Users from other organizations are denied ✗

Managing User Grants

Assigning Roles

// Via Management API
await managementService.addUserGrant({
  userId: "user-id",
  projectId: "project-id",
  projectGrantId: "grant-id", // Optional, for granted projects
  roleKeys: ["editor", "viewer"]
});

Updating Role Assignments

await managementService.updateUserGrant({
  userId: "user-id",
  grantId: "grant-id",
  roleKeys: ["admin"] // Replaces previous roles
});

Removing Role Assignments

await managementService.removeUserGrant({
  userId: "user-id",
  grantId: "grant-id"
});

Listing User Grants

See all of a user’s role assignments:
const grants = await managementService.listUserGrants({
  filters: [
    {
      userIdFilter: {
        userId: "user-id"
      }
    }
  ]
});

Project Grants and Roles

When granting a project to another organization, you specify which roles they can assign:
await projectService.createProjectGrant({
  projectId: "customer-portal-id",
  grantedOrganizationId: "partner-org-id",
  roleKeys: ["viewer", "editor"] // Only these roles available
});
Now the partner organization can:
  • Assign viewer and editor roles to their users ✓
  • Cannot assign admin role ✗
This enables secure multi-tenant role delegation.

Best Practices

  • Principle of Least Privilege: Grant only the permissions users need
  • Role Granularity: More specific roles are better than catch-all roles
  • Avoid Role Explosion: Don’t create too many similar roles
  • Document Roles: Clearly define what each role can do
  • Review Regularly: Audit role assignments quarterly
  • Use consistent naming across all projects
  • Group related roles using the group field
  • Use descriptive display names for the UI
  • Keep role keys short and URL-safe
  • Document role hierarchies if you use them
  • Always verify roles server-side, never trust client-side checks
  • Cache role checks to improve performance
  • Use middleware for common authorization patterns
  • Log authorization failures for security monitoring
  • Implement graceful degradation (show/hide features based on roles)
  • Consider organization context when checking roles
  • Use project grants to control cross-organization access
  • Enable projectAccessRequired for SaaS applications
  • Test authorization with users from different organizations
  • Document which roles are available via project grants
  • Enable authorizationRequired to prevent unauthorized access
  • Limit ORG_OWNER role assignments
  • Regularly audit role assignments and remove unused grants
  • Use separate roles for admin vs user functionality
  • Consider time-limited role assignments for temporary access

Common Authorization Patterns

Role-Based Access Control (RBAC)

The most common pattern - assign roles, check roles:
if (hasRole(user, 'admin')) {
  // Admin functionality
}

Hierarchical Roles

Higher roles inherit lower role permissions:
const roleHierarchy = {
  'admin': ['editor', 'viewer'],
  'editor': ['viewer'],
  'viewer': []
};

function hasPermission(userRole, requiredRole) {
  if (userRole === requiredRole) return true;
  return roleHierarchy[userRole]?.includes(requiredRole) || false;
}

Resource-Based Roles

Roles tied to specific actions on resources:
function canAccessResource(user, resource, action) {
  const requiredRole = `${resource}:${action}`;
  return hasRole(user, requiredRole);
}

if (canAccessResource(user, 'reports', 'read')) {
  // Show reports
}

Organization-Scoped Authorization

Check roles in the context of a specific organization:
function hasRoleInOrg(token, role, orgId) {
  const rolesClaim = token[`urn:zitadel:iam:org:project:${PROJECT_ID}:roles`];
  return rolesClaim?.[role]?.[orgId] !== undefined;
}

Troubleshooting

Cause: projectRoleAssertion is not enabledSolution:
await projectService.updateProject({
  projectId: "your-project-id",
  projectRoleAssertion: true
});
Cause: authorizationRequired is enabled but user has no role assignmentsSolution: Assign at least one project role to the user
await addUserGrant({
  userId: "user-id",
  projectId: "project-id",
  roleKeys: ["viewer"]
});
Cause: Role was removed from project grantSolution: Update the project grant to include the necessary roles
await projectService.updateProjectGrant({
  projectId: "project-id",
  grantedOrganizationId: "org-id",
  roleKeys: ["viewer", "editor"] // Include all needed roles
});
Cause: User has different role assignments in different organizationsSolution: This is expected behavior. Check the organization context in your authorization logic.

Users

Users are assigned roles through user grants

Organizations

Organizations provide context for role assignments

Projects

Projects define the custom roles available for assignment