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.
| Family | Permissions |
|---|---|
| User Management | 3 |
| Client Management | 4 |
| Role & Authorization | 5 |
| Realm Management | 3 |
| Identity Providers | 2 |
| Events & Audit | 2 |
| Groups | 1 |
| Client Scopes | 3 |
| Webhooks | 3 |
| Email Templates | 2 |
| Organizations | 2 |
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
| Permission | Description |
|---|---|
create_client | Create new OAuth2 clients |
manage_authorization | Write the realm’s authorization rules (schema, policies, templates) and switch authorization on or off for the realm |
manage_clients | Update and delete clients |
manage_events | Manage audit event configuration |
manage_identity_providers | Configure external identity providers |
manage_realm | Update realm settings |
manage_users | Create, update, and delete users |
manage_roles | Create, update, and delete roles |
manage_webhooks | Configure webhook subscriptions |
manage_client_scopes | Manage client scopes and protocol mappers |
manage_email_templates | Create, update, and delete email templates |
manage_organizations | Create, update, and delete organizations, their groups and their members |
Query
| Permission | Description |
|---|---|
query_clients | List and search clients |
query_groups | List and search groups |
query_realms | List and search realms |
query_users | List and search users |
query_webhooks | List and search webhooks |
query_client_scopes | List and search client scopes |
View
| Permission | Description |
|---|---|
view_authorization | Read those rules, validate and simulate policies, and see which policies decided an evaluation |
view_clients | View client details |
view_events | View audit events |
view_identity_providers | View identity provider details |
view_realm | View realm details |
view_users | View user details |
view_roles | View role details |
view_webhooks | View webhook details |
view_client_scopes | View client scope details |
view_email_templates | View email templates |
view_organizations | View organizations, their groups and their members |
Evaluate
| Permission | Description |
|---|---|
evaluate_authorization | Ask 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.