Role Bindings
Role bindings connect roles with public keys.
For example, to grant a user permission to access a resource, you must first ensure that you have a role that defines the permissions. Then, you bind that role to the user's public key.
A role binding contains the following fields:
- a unique ID - We recommend using UUIDs as IDs.
- a name - This is used for easy identification in queries.
- a role - This is the ID of the role you wish to bind.
- an optional expiry time - See Expiry below.
- optional attributes - See Attributes below.
Expressions
Expressions are a way of conditionally applying role bindings and provide a flexible way to restrict access to resources. An expression is evaluated at runtime against the properties of the resource being accessed. This allows you to grant permissions based on criteria instead of a fixed list of instance IDs.
Example: document.owner == public_key - This expression grants access only if the owner field of the resource matches the public_key of the subject attempting to access it.
Expressions are handy when combined with is_universal. It allows a role binding to be used by any public key. You may have noticed that most M10 API requests have a field called owner. The owner field does not give any permissions. Instead, we often create role bindings with an expression like document.owner == public_key.
A role binding supports a list of expressions. Each collection in a role can be assigned its own expression. We use MQL to write Expressions, which has a syntax similar to Rust, and is detailed in the MQL documentation.
Expressions in role-bindings work for all collections that are “registered” (i.e. that provide a type environment) in the system. For collections that are not set up that way, expressions will not work.
Currently supported collections:
- account-metadata
- account-set
- banks
- ledger-account
- role-binding
- roles
The expression compiler supports the following operations:
- Literals: Numbers, strings, booleans, and bytes.
- Infix operations: Boolean “and”/“or” and equality checks are allowed. (Other infix operators like arithmetic, union/intersect, etc. are not supported.)
- Field access: If the object's type is known (e.g. a document with an “owner” field), then you can access its fields.
The following expressions are allowed:
| Field | Description |
|---|---|
owner | The owner field does not provide any permissions. Instead, we often create role bindings with an expression like document.owner == public_key. |
Permission to create role bindings
To create, update, or delete a role-binding, a principal must hold GRANT coverage over every collection and scope in the role being bound – not just role-bindings: [Create]. This constraint is enforced at the ledger level. See The GRANT Verb for a full explanation of the delegation model.
For example, to add a new public key (from a second mobile device) to your account, you need role-bindings: [Create] and GRANT on the scopes covered by the role you are binding.
You cannot grant someone more permissions than you hold GRANT authority over. This applies to both creating new role-bindings and updating existing ones.
Permission to delete role bindings
Deleting a role-binding requires either GRANT or REVOKE coverage over every collection and scope in the bound role. REVOKE is a narrower authority that allows deletion of role-bindings without the ability to create or update them – useful for security operations roles that need to terminate access without being able to mint new permissions. See The REVOKE Verb for details.
Expiry
Role bindings support an optional expiry time via the expires_at field. When set, the value is a Unix timestamp in milliseconds. If the current time is past the expiry time, the binding is silently ignored during authorization – it is treated as if it does not exist. If expires_at is absent, the binding never expires.
This is useful for granting temporary access, such as time-limited sessions or one-off operational permissions.
Creating a role binding with expiry
- Rust
- CLI
Expired bindings are not automatically deleted. They remain stored but are ignored during authorization checks.
Attributes
If the role of role-binding contains when-statement with custom variables, their actual values
must be specified using --attributes field.
Example:
role:
rule:
- collection: ledger-accounts
verbs: [Initiate, Transact, Commit]
when: "transfer.amount < transfer_limit"
types:
- [transfer_limit, U64]
create:
roleBinding:
attributes:
transfer_limit: 10000
Labels
Role bindings support optional labels – key-value metadata pairs that can be attached for categorization, organization, and filtering. Both keys and values are strings with a maximum length of 100 characters each.
Labels can be set at creation time and updated later. When listing role bindings, you can filter by labels instead of name, description, or subject.
Creating a role binding with labels
- Rust
- TypeScript
- Dart
- CLI
Updating labels
- Rust
- TypeScript
- Dart
- CLI
Filtering by labels
- Rust
- TypeScript
- Dart
- CLI
The --labels filter is mutually exclusive with --name, --description, and --subject.
Model
The data model for an M10 hierarchical ledger account is defined in rbac.proto:
API Requests
Create
To create a role-binding:
- Rust
- TypeScript
- Dart
- CLI
Update
To add or remove a subject from a role-binding in the CLI, you can also use m10 update role-binding-subjects with the add or remove verb.
- Rust
- TypeScript
- Dart
- CLI
Delete
To delete a role binding:
- Rust
- TypeScript
- Dart
- CLI
List (Find)
List role bindings by --name (text or partial text), --subject (public key), --description (text or partial text), or --labels (key=value pairs). These filters are mutually exclusive:
- Rust
- TypeScript
- Dart
- CLI
Get
To get a role binding:
- Rust
- TypeScript
- Dart
- CLI
To submit requests over HTTP/1 instead of HTTP/2 with the SDKs, use the http option.