FerrisKeyDocs

Roles & Permissions

Permissions in FerrisKey are bits in a 64-bit integer. A role is a named bundle of them, and an authorization check is a single bitwise AND.

How it works

Each permission maps to a unique power of two:

CreateClient        = 0b0000...0001  (bit 0)
ManageAuthorization = 0b0000...0010  (bit 1)
ManageClients       = 0b0000...0100  (bit 2)
...

A role stores its permissions as a single u64 bitmask, the OR of all included permission bits. Checking one permission is a single AND:

fn has_permission(user_permissions: u64, required: u64) -> bool {
    user_permissions & required == required
}

An operation accepts several permissions, and any one of them grants

Most operations are guarded by a set of permissions rather than a single one, and holding any member of the set is enough. Reading a federation provider, for example, accepts either manage_realm or view_realm. So a manage_* permission generally implies the matching view_* and you do not need to grant both.

Permissions reference

The console groups the 30 permissions into eleven families, which is also the most convenient way to reason about them: you rarely grant one permission, you grant a family’s worth of read or a family’s worth of write.

FerrisKey role permissions editor showing permission families with their enabled counts FerrisKey role permissions editor showing permission families with their enabled counts
FamilyPermissions
User Management3
Client Management4
Role & Authorization5
Realm Management3
Identity Providers2
Events & Audit2
Groups1
Client Scopes3
Webhooks3
Email Templates2
Organizations2

Editing a role’s permission set is one call:

PATCH /realms/{realm_name}/roles/{role_id}/permissions

It replaces the set rather than merging into it, so send the full list you want the role to end up with.

The API and the CLI both take permissions by their snake_case name, so manage_realm, not ManageRealm and not realm:manage. An unrecognized name is handled differently depending on who saves the role. A caller holding manage_realm has it dropped silently, which is worth remembering when a role looks like it granted nothing. Any other caller is refused with a 403 (cannot grant a permission you do not hold), because a name that matches no permission is also a permission the caller cannot hold.

Manage

PermissionDescription
create_clientCreate new OAuth2 clients
manage_authorizationWrite the realm’s authorization rules (schema, policies, templates) and switch authorization on or off for the realm
manage_clientsUpdate and delete clients
manage_eventsManage audit event configuration
manage_identity_providersConfigure external identity providers
manage_realmUpdate realm settings
manage_usersCreate, update, and delete users
manage_rolesCreate, update, and delete roles
manage_webhooksConfigure webhook subscriptions
manage_client_scopesManage client scopes and protocol mappers
manage_email_templatesCreate, update, and delete email templates
manage_organizationsCreate, update, and delete organizations, their groups and their members

Query

PermissionDescription
query_clientsList and search clients
query_groupsList and search groups
query_realmsList and search realms
query_usersList and search users
query_webhooksList and search webhooks
query_client_scopesList and search client scopes

View

PermissionDescription
view_authorizationRead those rules, validate and simulate policies, and see which policies decided an evaluation
view_clientsView client details
view_eventsView audit events
view_identity_providersView identity provider details
view_realmView realm details
view_usersView user details
view_rolesView role details
view_webhooksView webhook details
view_client_scopesView client scope details
view_email_templatesView email templates
view_organizationsView organizations, their groups and their members

Evaluate

PermissionDescription
evaluate_authorizationAsk the realm for access decisions, as an application enforcing them does

evaluate_authorization is the thirtieth permission, bit 29. It grants no right over the rules themselves, and it only works for a service account: the evaluation endpoints refuse every other caller with a 403, including a user who holds manage_realm. Give it to a role, and give that role to the service account of the client that enforces your policies. The Authz module explains the rest.

Actions, and how a check is resolved

Permissions are what a role stores. They are not how the code names an operation.

Every authorization-guarded operation has a stable action id of the form resource_type:verb, such as client:create, user_role:assign or security_event:read. There are 110 of them, 15 of which belong to the Authz module itself (authorization:evaluate and the authorization_policy:*, authorization_schema:*, authorization_state:* and authorization_template:* ids). The names are stable: an authorization engine resolves an action to the set of permissions that grant it, and the mapping is kept in one decision table rather than scattered across call sites.

Two things follow that matter when you design roles. The vocabulary you grant and the vocabulary the code checks are different layers: you assign manage_users, the code asks whether user:update is allowed, and the engine bridges the two. And some checks short-circuit on identity. A handful of operations allow the subject when the subject is the target, whatever their permissions, which is how a user reads their own profile without holding view_users. Self-service goes through its own endpoints rather than the admin ones.

Not the rules your applications are judged by

This catalogue is internal. FerrisKey resolves it in-process to guard its own admin API, and the evaluation endpoints do not use these ids. The Authz module answers AuthZEN requests about your own applications, with Cedar policies your realm writes. Its actions come from the realm’s schema, such as document:read.

Role mappings

Roles are attached to users, including service account users, through role mappings. A user’s effective permissions are the bitwise OR of every role bitmask assigned to them.

Take a user with two roles:

  • Viewer, holding view_users | view_clients = 0b...10100
  • User Manager, holding manage_users | query_users = 0b...01010

The effective mask is 0b...11110: they can view and manage users, view clients, and query users.

Managing roles

GET    /realms/{realm_name}/roles
POST   /realms/{realm_name}/roles
GET    /realms/{realm_name}/roles/{role_id}
PUT    /realms/{realm_name}/roles/{role_id}
DELETE /realms/{realm_name}/roles/{role_id}
PATCH  /realms/{realm_name}/roles/{role_id}/permissions

A client role is created under its client instead: POST /realms/{realm_name}/clients/{client_id}/roles.

A role's scope is fixed, and deletion is not retroactive

A role is attached to the realm or to a client at creation, and moving it between scopes is not supported, so recreate it instead.

Deleting a role permanently removes every user assignment it had, but tokens already issued stay valid until they expire. The window between the two is the access token lifetime, so revoke the sessions as well when you are removing a role because it should never have been granted.

Required permissions: view_roles, manage_roles or manage_users to read (manage_users is on the list because assigning a role requires seeing it). manage_roles to write. manage_realm grants everything.

Realm and client roles

A realm role is defined on the realm and applies across all its clients. A client role is scoped to one client and only means something in that client’s context. Both are assigned to users the same way, and role ids are unique across the two scopes.

When a user authenticates, their roles are resolved and written into the access token as claims through protocol mappers, so a resource server can authorize a request without calling back to FerrisKey.