Roles and permissions
Describe what people may do with roles, assign them to users, groups, clients or realms, and check them in your API from the access token.
A role is a named set of permissions — billing-admin, editor, support-agent — that Authio writes into the access tokens of everyone who holds it. Your API reads the token and decides; Authio never calls your code to ask.
Roles belong to your account. Where a role is assigned decides who receives it and under which claim it arrives: to a user directly, through a group, to everyone who signs in through a client, or to everyone in a realm.
How a role is built
A role carries a list of claims. The type of each claim is a permission (invoice, documents, reports) and its value is a method the role allows on it (read, write, approve). A role that lets people read and write invoices carries two claims of type invoice, with the values read and write.
| Part | Rules |
|---|---|
| Name | Up to 256 characters, unique in the account regardless of case. default-system and admin-system are reserved for the platform. |
| Description | Up to 500 characters, shown in the console only. |
| Claims | A permission of up to 256 characters and a method of up to 1,024. Names the protocol writes, such as sub, aud or scope, are refused. |
| Status | An inactive role is left out of every token and cannot be assigned. |
Global and exclusive roles
A role created under Global Roles can be assigned anywhere in the account. A role created from a realm, a client, a user or a group is exclusive to that owner: only the owner, and what lives inside it, can ever receive it. A role exclusive to a client, for example, only reaches tokens that client issues. The owner is set when the role is created and never changes.
Create a role
Open the roles list
In the console, open Permissions › Global Roles and choose New Role. For a role exclusive to a realm, a client, a user or a group, start from that record’s page instead.
Name it and add its permissions
Give it a name and a description, then add one claim per permission and method: type
invoicewith valueread, typeinvoicewith valuewrite.Assign it
A role does nothing until someone holds it. The next section lists the four ways to assign one.
Assign roles
There are four ways to give someone a role, and they add up: a person holds every role that reaches them by any of them.
| Assigned to | Where in the console | Who receives it | Claim |
|---|---|---|---|
| A user | The Permissions step of the user’s edit screen, in Users | That person | user_resource_access |
| A group | Groups | Every member, in tokens issued by the group’s client | user_resource_access |
| A client | Granted roles, on the client’s Access tab | Everyone who signs in through that client | client_resource_access |
| A realm | Granted roles, on the realm’s Access and rules tab | Everyone in the realm | realm_resource_access |
Groups
A group is a named set of users of one client that hands its roles to every member at once. Create it in Groups with New group — pick the realm and the client, name it and choose the roles it grants — then add members from its page. Any user of the realm can join a group of one of the realm’s clients.
- Roles are read through the membership each time a token is issued, never copied onto the user: removing someone from the group takes the roles away from their next token.
- Saving a group’s roles replaces the ones it granted before; they do not add up.
- An inactive group keeps its members and grants nothing.
- Groups belong to one client and do not nest.
Roles in the access token
Roles reach the access token only when it was granted the roles scope. roles is a default scope: when it is enabled on the client, it is granted even if the authorization request does not name it. The ID token never carries roles.
Each origin has a claim of its own. Each claim maps a role name to its permissions, and each permission to the methods the role allows:
{
"iss": "https://authio.api.hyperio.tech/realms/acme",
"sub": "6f1c2e7a-4b8d-4f0e-9a51-3c2d7e8b9f10",
"aud": "https://api.acme.com",
"azp": "acme-web",
"scope": "openid profile roles",
"user_resource_access": {
"billing-admin": { "invoice": ["read", "write"] }
},
"client_resource_access": {
"member": { "invoice": ["read"], "reports": ["read"] }
},
"realm_resource_access": {
"employee": { "directory": ["read"] }
},
"exp": 1791372600
}| Claim | Holds |
|---|---|
user_resource_access | The roles assigned to the person directly, and those of the groups they belong to in the client that issued the token. |
client_resource_access | The roles the issuing client grants to everyone who signs in through it. |
realm_resource_access | The roles the realm grants to everyone in it. |
A claim is left out when it would be empty. Inactive roles, and roles withheld by a suspension in force, are dropped from all three.
Machine credentials work the same way under a claim of their own: a consumer’s roles arrive in consumer_resource_access. See Machine-to-machine.
Check permissions in your API
Validate the token first — signature, issuer, audience and expiry, as Resource servers shows. Then take the union of the three claims and of every role in them: a permission granted by any role the caller holds is granted.
Check permissions, not role names
billing-admin breaks on a rename; code that tests for invoice and write does not.import type { JWTPayload } from 'jose'
type ResourceAccess = Record<string, Record<string, string[]>>
const ROLE_CLAIMS = [
'user_resource_access',
'client_resource_access',
'realm_resource_access',
] as const
export function can(payload: JWTPayload, permission: string, method: string) {
return ROLE_CLAIMS.some((claim) =>
Object.values((payload[claim] ?? {}) as ResourceAccess).some((granted) =>
granted[permission]?.includes(method),
),
)
}In ASP.NET Core, wrap the check in a policy. MapInboundClaims = false keeps the claim names exactly as Authio writes them:
using Microsoft.AspNetCore.Authentication.JwtBearer;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://authio.api.hyperio.tech/realms/acme";
options.Audience = "https://api.acme.com";
options.MapInboundClaims = false;
});
builder.Services.AddAuthorization(options =>
options.AddPolicy("invoices.write", policy =>
policy.RequireAssertion(context => context.User.Can("invoice", "write"))));
var app = builder.Build();
app.MapPost("/invoices", () => Results.Ok())
.RequireAuthorization("invoices.write");
app.Run();When changes take effect
Roles are copied into the token when it is issued. Granting, removing or deactivating a role reaches a person at their next token — a refresh or a new sign-in — and never a token already issued.