Skip to content

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.

PartRules
NameUp to 256 characters, unique in the account regardless of case. default-system and admin-system are reserved for the platform.
DescriptionUp to 500 characters, shown in the console only.
ClaimsA 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.
StatusAn 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

  1. 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.

  2. Name it and add its permissions

    Give it a name and a description, then add one claim per permission and method: type invoice with value read, type invoice with value write.

  3. 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 toWhere in the consoleWho receives itClaim
A userThe Permissions step of the user’s edit screen, in UsersThat personuser_resource_access
A groupGroupsEvery member, in tokens issued by the group’s clientuser_resource_access
A clientGranted roles, on the client’s Access tabEveryone who signs in through that clientclient_resource_access
A realmGranted roles, on the realm’s Access and rules tabEveryone in the realmrealm_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:

Access token payloadJSON
{
  "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
}
ClaimHolds
user_resource_accessThe roles assigned to the person directly, and those of the groups they belong to in the client that issued the token.
client_resource_accessThe roles the issuing client grants to everyone who signs in through it.
realm_resource_accessThe 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

A role name is a label an administrator can change. Code that tests for billing-admin breaks on a rename; code that tests for invoice and write does not.
permissions.tsTypeScript
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:

Program.csC#
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.

An access token issued before the change keeps the old roles until it expires, so the access token lifetime is how long a removed permission keeps working. Shorten it on the client if removals must apply sooner.

Next steps