Advanced features - Role and RBAC
What is a Role?
A role is Vault’s RBAC primitive: a named permission unit that is bound to a realm, attached to users and clients, and ultimately carried into the access token — where your services compare it against their own roles to decide what the current user may do.
Roles are scoped to the realm. A realm can define many roles, and each role can be attached to any number of clients and users within that realm.
Anatomy of a role
A role has two identifiers, and the pairing is deliberate:
| Field | Meaning | Example |
|---|---|---|
label |
The human-readable name — the key of the role map | readonly, admin |
code |
The opaque value used everywhere at runtime | vault-3f2a... |
default |
Whether the role is auto-attached to every user in the realm | true / false |
The core idea is a key/value indirection: the label is what you think in, the code is what machines talk in.
The label can be treated as a key and the code as its value. Clients can retrieve their role map — a plain { [label]: code } object — and use it to translate codes back into meanings that make sense to them.
Each service gives the code its own logical meaning. readonly might map to the code readonly_code and admin to admin_code — the label describes the intent, the code is just the opaque token that carries it.
Why codes at all?
Why not put plain role names in the token? Three reasons:
- The token stays self-contained. The access token carries an array of role codes; checking membership is a plain code comparison — no lookup, no strings that leak your application’s permission vocabulary to anyone holding a decoded token.
- Semantics live client-side. Vault doesn’t know what
readonlymeans for your service. It only guarantees that the codereadonly_codeis the one you mapped toreadonly. Each client wraps the code in its own logical meaning. - Renaming is free. Change the label, keep the code — every issued token remains valid, because tokens only ever carry codes.
Attaching roles
Roles attach to two places, and the combination creates the client-scoped RBAC grid:
- To clients — defines which roles belong to that client’s vocabulary. A client only ever checks against roles attached to it.
- To users — defines which roles a user holds.
A user can hold two roles attached to Client 1 and three roles attached to Client 2. Neither client sees the other’s roles: each client checks against its own roles only. A user’s total role set is the union across all clients, but at runtime, each client filters to the roles it has attached itself.
Default roles (default: true) are automatically attached to all users bound to that realm — not to specific clients. Attachment to clients still has to be configured per client.
Roles in the access token
When Vault issues an access token, the roles attached to the user (filtered by scope) are serialized as an array of codes:
{
"roles": ["admin_code", "readonly_code"]
}
Verifying “is this user an admin?” is a two-step lookup, fully local:
- At startup (or whenever you fetch your configuration), retrieve the client’s role map:
{ "readonly": "readonly_code", "admin": "admin_code" } - On each request, for a protected resource, check whether the code you care about is in the token’s
rolesarray.
No call to Vault, no per-request lookup — the same local-verification property that governs token checking applies to roles.
Retrieving the role map
Clients fetch their role map the same way they fetch their keyset: a single authenticated call against the client endpoints, using their client_id and client_secret.
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/v1/roles |
| Body | {clientId: client_id, clientSecret: client_secret} |
| Response | The role map for this client: { [label]: code } |
Example response:
{
"readonly": "readonly_code",
"admin": "admin_code"
}
The map contains only the roles attached to this client — it is already scoped to the requesting client, so no filtering is required on your side.
Like the keyset, this is a startup-time call: fetch once, cache the result, and use it to resolve the codes in every access token thereafter. Re-fetch when you add new roles to the client, or alongside your periodic keyset refresh if you keep one.
:::callaout warning
Vault does not have any webhook or other event dispatching mechanism yet, so refetching the roles and other resources are the client’s duty.
:::
The full picture
Reading the diagram: roles are defined in the realm, attached (dotted) to both clients and users. The user holds roles; the clients declare which roles they understand. When Vault signs the token, only codes travel — and each client resolves them through its own role map.
A concrete walk through the model:
- Realm setup — you create two roles in the realm:
readonly(codeasjdoaidwa) andadmin(codegjefokejflw). - Client attachment — Client 1 attaches both roles; Client 2 attaches only
admin. - User attachment — a user is attached to
readonlyandadmin. Another role exists that is attached to Client 2 only — this user holds none of Client 2’s roles, so Client 2 sees an empty role set for them. - Token issuance — the access token carries
roles: ["admin_code", "readonly_code"]. - Runtime check — Client 1 compares the array against its own role map and finds both
readonlyandadmin. Client 2 compares the same array against its role map and only recognizesadmin.
The same token, two different permission views — because permission meaning is resolved per client, not centrally.
Users can hold roles from multiple apps that are in the same realm. It’s the client’s internal logic to use the role codes that it needs. Vault won’t filter based on what the client sees and what the user has, it sends everything that the user has.
Summary
- Roles are bound to the realm, attached to users and clients
label= key,code= value; the token carries codes only- Clients retrieve their role map (
{ [label]: code }) and give each code its own logical meaning defaultroles are auto-attached to every user in the realm- Each client checks only against its own attached roles
- Role verification is local — the access token’s
rolesarray is compared against the client’s map, with no call to Vault
In a distributed system, different services might have different RBAC set in place for resource access. Vault can send all roles attached to your entire system and each service filters based on what role has attached to it.