About Authorization
There are two ways in which clients can authenticate to the ledger, the first by signing the mutation bodies, and the second by sending a JWT token. See About Authentication for more details about it.
These two forms of authentication allow users to say who they are, but do not guarantee that they have privileges to access the ledger.
Access Strategies
Minka ledgers support two access strategies that determine how access control is managed:
Record-Based Access Strategy (Default)
In record-based access strategy, access control rules are defined directly on individual records (ledger, domains, wallets, etc.). The authorization layer checks permissions by following the hierarchy:
record → ledger → server
This is the traditional approach where each record can define its own access rules, including ledger and domains - which applies to children records.
Policy-Based Access Strategy (Recommended)
In policy-based access strategy, access control is managed globally through policies without requiring attachment to specific records. The authorization layer evaluates active policies globally and ignores access rules defined directly on individual records.
active policies → server
Key Differences:
- Record-based: Access rules are attached to individual records
- Policy-based: Access rules are defined in global policies
- Migration: Ledgers can be migrated from record-based to policy-based, but not vice versa
- Policy evaluation: Policy-based ledgers only consider active policies with
schema: 'access'
Access Rules and Policies
An access rule can be either a simple rule or a reference to a policy. See About Security Policies for more details about policies.
Access permission rules follow the following format.
type Rule: {
/**
* Defines which action the access rule is assigned to
*/
action: AccessAction
/**
* Defines which ledger record class the access rule is assigned to
*/
record?: AccessRecord
/**
* Define conditions that the subscriber of the request body
* must follow to grant access.
*/
signer?: AccessSigner
/**
* Define claims and metadata about the JWT token to grant access
*/
bearer?: AccessBearer
}
type AccessPolicy: {
/**
* Attaches a policy to the access rule
*/
policy: string
}
type AccessRule = Rule | AccessPolicy
Where
type AccessAction:
'any' | // Applies to all actions below
'access' | // Defines permissions for accessing child records of a record
'create' | // Defines permissions for creating a record
'read' | // Defines permissions for reading a record
'drop' | // Defines permissions for dropping a record
'update' | // Defines permissions for updating a record
'lookup' | // Defines permissions for looking up from a record
'assign-signer' | // Defines permissions for assigning a signer to a circle
'remove-signer' | // Defines permissions for unassigning a signer from a circle
'issue' | // Defines permissions for issuing a symbol
'destroy' | // Defines permissions for destroying a symbol
'spend' | // Defines permissions for spending from a wallet
'limit' | // Defines permissions for limiting a wallet
'commit' | // Defines permission for comitting an intent with a proof
'abort' // Defines permission for aborting an intent with a proof
The create
action is not valid when defining access to a specific record - record level rules - since the record already exists. Access rules with create
action are defined on parent records, usually this is ledger or server.
access
is also not valid for records without children, it should be defined either at server or ledger levels. See the section about child records access constraints
type AccessRecord:
'any' | // Applies all records below
'server' | // Defines constraints to access the server
'ledger' | // Defines constraints to perform actions on ledgers
'signer' | // Defines constraints to perform actions on signers
'symbol' | // Defines constraints to perform actions on symbols
'wallet' | // Defines constraints to perform actions on wallets
'intent' | // Defines constraints to perform actions on intents
'intent-proof' | // Defines constraints to perform actions on intent proofs
'effect' | // Defines constraints to perform actions on effects
'bridge' | // Defines constraints to perform actions on bridges
'circle' | // Defines constraints to perform actions on circles
'circle-signer' | // Defines constraints to perform actions on circle signers
'policy' | // Defines constraints to perform actions on policies
'schema' | // Defines constraints to perform actions on schemas
'anchor' | // Defines constraints to perform actions on anchors
'domain' // Defines constraints to perform actions on domains
Access constraints must be defined respecting the hierarchy of server > ledger > record which means:
- The
server
record rule cannot be set on ledger or record level rules - The
ledger
record rule cannot be set on ledger or record level rules - The property
record
should me omitted or defined with the same value of the record's type when defining access control on record level i.e it's not allowed to define access rules to managesymbol
within awallet
record.
The property record
can be omitted when defining access rules, and in this case it indicates the access rule is intended to be applied to record which defines the access rule.
type CircleConstraint = string
type CircleAggregation = {
$in: Array<CircleConstraint>
}
type AccessCircle = CircleConstraint | CircleAggregation
enum RecordOwnership {
Creator = 'creator'
}
type AccessSigner = SignerConstraint | SignerAggregation
type SignerConstraint = {
handle?: string // defines constraints for signer handle
format?: string // defines constraints for signer format
public?: string // defines constraints for signer public key
$circle?: AccessCircle // defines constraints for signer circles
schema?: string // defines constraints for signer schema
$record?: RecordOwnership // defines constraints for the relationship between
// the signer and the target record
$ledger?: RecordOwnership // defines constraints for the relationship between
// the signer and the active ledger
}
type SignerAggregation = {
$in: Array<SignerConstraint> // signature must respect at least
// one of the constraints defined in the list
}
/**
* @example
*
* Defining a signer aggregation rules which
* requires the signature of 'owner' signer or
* a key pair with` '1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU='
* public key.
*/
{
...,
signer: {
$in: [
{
handle: 'owner',
},
{
public: '1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU='
}
]
}
}
/**
* @example
*
* Defining a signer rule which requires the signature
* of a signer from circle 'admin'
*/
{
...,
signer: {
$circle: 'admin'
}
}
/**
* @example
*
* Defining a signer rule which requires the signature
* of the record creator
*/
{
...,
signer: {
$record: 'creator'
}
}
/**
* @example
*
* Defining a signer rule which requires the signature
* of the ledger creator
*/
{
...,
signer: {
$ledger: 'creator'
}
}
signer
property is only valid for mutations, since a GET Http request doesn't have a body.
Since a record can hold multiple signatures, these access rules defines that at least one of the signers must fulfill.
type AccessBearer = {
/**
* Defines access rule regarding the issuer of bearer token
*
* @example company.org
*/
iss?: string
/**
* Defines access rule regarding the subject of bearer token
*
* @example admin
*/
sub?: string
/**
* Defines access rule regarding the audience of bearer token
*
* @example ledger
*/
aud?: string
/**
* Defines if the request hash is mandatory
*
* @example true
*/
hsh?: boolean
/**
* Defines the key required to verify the signature of bearer token
*
* @example WAweF9PHlboQoW0z8NqhZXFmzUTaV74NRFAd/aILprE=
*/
$signer?: SignerConstraint
} | BearerAggregation
/**
* @example
*
* Defining a bearer aggregation rule which
* requires the token to be signed by 'owner' signer or
* a key pair with` '1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU='
* public key.
*/
{
...,
bearer: {
$in: [{
$signer: {
handle: 'owner'
}
}, {
$signer: {
public: '1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU='
}
}]
}
}
/**
* @example
*
* Defining a bearer rule which requires the token to be signed
* by a signer from circle 'admin'
*/
{
...,
bearer: {
$signer: {
$circle: 'admin'
}
}
}
Both AccessSigner and AccessBearer are matcher objects. Please don’t confuse them with Referenced Records. Matcher objects are more flexible because the whole object can be matched instead of just handle.
Access Control Flow
The access control flow depends on the ledger's access strategy:
Record-Based Access Flow
Child record access constraints work as filters and describe minimum conditions required for accessing a child record. They can be set on any record that has child records in the ledger. Most common records like that are ledger
and server
records.
Those access constraints are checked before validating the actual action
the user wants to perform, and it follows the hierarchy:
server → ledger → record (top down approach)
Examples:
-
user A
wants toread
asymbol
, so firstly it must fulfill requirements for accessing theserver
and then fulfill requirements to access theledger
. Only after those requirements are satisfied, the ledger will check if theuser A
can perform theread
operation on thesymbol
. -
user B
wants tocreate
aledger
, so it should have permissions to access theserver
first. After that the ledger will check if the user has permissions tocreate
aledger
.
Policy-Based Access Flow
In policy-based ledgers, access control is simplified:
server → active policies (global evaluation)
The system:
- Checks server-level access constraints
- Evaluates all active policies with
schema: 'access'
globally - Ignores access rules defined on individual records
- Applies policies based on the target record type and domains
Important for Policy-Based Ledgers:
- Individual record access rules are ignored
- Only active policies with
schema: 'access'
are evaluated - Policies can be domain-specific or global
- Policy status (
active
,inactive
,created
) determines enforcement
Examples: Record-Based Access Rules
Examples of child record access rules at server level (applies to both strategies):
/**
* Defines constraints to access the server. To access the server the
* user must send a token signed by the specified key.
*/
{
action: 'access',
bearer: {
$signer: {
public: '1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU='
}
}
}
/**
* Defines constraints to access a ledger. To access a ledger the
* user must send a token signed by the specified key.
*/
{
action: 'access',
record: 'ledger',
bearer: {
$signer: {
public: '1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU='
}
}
}
/**
* Defines constraints to access any record. To access any record the
* user must send a token signed by the specified key.
*/
{
action: 'access',
record: 'any',
bearer: {
$signer: {
public: '1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU='
}
}
}
Examples of child record access rules at ledger level (record-based ledgers):
/**
* Defines constraints to access a ledger. To access the ledger the user
* must send a token signed by any signer.
*/
{
action: 'access',
bearer: {
$signer: {}
}
}
/**
* Defines constraint to access any wallet. The access any wallet the user
* must send a token signed by any signer.
*/
{
action: 'access',
record: 'wallet',
bearer: {
$signer: {}
}
}
Server level rules
Server level rules are defined via an environment variable named SERVER_ACCESS_RULES
set on ledger API.
Access to any record and action can be defined at server level since it's the root level of access control.
/**
* server access rules that restrict server access to token-signed users
* and allow any signer to create ledger instances.
*/
[
{
"action": "access",
"bearer": {
"$signer": {}
}
},
{
"action": "create",
"record": "ledger",
"signer": {}
}
]
Ledger level rules
Record-Based Ledgers
In record-based ledgers, ledger level rules are defined in the access
property when creating a ledger instance.
Access to any record can be defined at ledger level, except from server
.
/**
* Payload of a record-based ledger which allows every
* signer to perform any operation on any ledger record.
*/
{
"hash": "99dbff500c451a7480ce2dc5928875478cb47b2a358baeaf4064a60eadd897a5",
"data": {
"handle": "some_ledger",
"signer": "some_signer",
"config": {
"access.strategy": "record-based"
},
"access": [
{
"action": "any",
"record": "any",
"bearer": {
"$signer": {}
}
}
]
},
"meta":{
"proofs":[
{
"method": "ed25519-v2",
"public": "1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU=",
"result": "lPZsbs+BlWnu5Y5SMWH8AflAFzfKIvfvCgQ2dxZHC6D0j91N5o6F90hiWe6B8JV4MqSYsfGTzb9Rpfz8ecSbAg==",
"digest": "3f294cf7533bf5c24675ad238fbfd235860fcc2bc02f8d666894726b7f4ce523",
"custom": {
"moment": "2023-02-20T21:42:10.279Z"
}
}
]
}
}
Policy-Based Ledgers
In policy-based ledgers, access control is managed through policies rather than ledger-level access rules. The ledger's access
property may contain policy references, but individual record access rules are ignored.
/**
* Payload of a policy-based ledger that uses policies for access control
*/
{
"hash": "88dbff500c451a7480ce2dc5928875478cb47b2a358baeaf4064a60eadd897a5",
"data": {
"handle": "policy_ledger",
"signer": "some_signer",
"config": {
"access.strategy": "policy-based"
},
"access": [
{
"action": "access"
},
{
"policy": "ledger-owner"
},
{
"policy": "team-access"
}
]
},
"meta":{
"proofs":[
{
"method": "ed25519-v2",
"public": "1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU=",
"result": "lPZsbs+BlWnu5Y5SMWH8AflAFzfKIvfvCgQ2dxZHC6D0j91N5o6F90hiWe6B8JV4MqSYsfGTzb9Rpfz8ecSbAg==",
"digest": "3f294cf7533bf5c24675ad238fbfd235860fcc2bc02f8d666894726b7f4ce523",
"custom": {
"moment": "2023-02-20T21:42:10.279Z"
}
}
]
}
}
Record level rules
Record-based ledgers only: Record level rules are only enforced in ledgers with access.strategy
set to record-based
. In policy-based ledgers, these rules are ignored in favor of global policy evaluation.
Record level rules are defined in the access
property when creating a record.
It can be used to define access to read
and update
the target record.
/**
* Payload of a signer which can be updated and read by the signer who
* created the record. (Only enforced in record-based ledgers)
*/
{
"hash":"914816628f3481e57a246d4906b90e8b0125fb0f508dd24b5f1a849545f2a5d1",
"data":{
"handle": "some_signer",
"public": "1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU=",
"format": "ed25519-raw",
"access": [{
"action": "read",
"bearer": {
"$signer": {
public: "1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU="
}
}
}, {
"action": "update",
"signer": "1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU=",
"bearer": {
"$signer": {
public: "1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU="
}
}
}]
},
"meta": {
"proofs": [
{
"method": "ed25519-v2",
"public": "1bZhhSgwDZ5C9pXsD2Q79A7rhxAZPBM3912G+lW/xIU=",
"result": "lPZsbs+BlWnu5Y5SMWH8AflAFzfKIvfvCgQ2dxZHC6D0j91N5o6F90hiWe6B8JV4MqSYsfGTzb9Rpfz8ecSbAg==",
"digest": "3f294cf7533bf5c24675ad238fbfd235860fcc2bc02f8d666894726b7f4ce523",
"custom": {
"moment": "2023-02-20T21:42:10.279Z"
}
}
]
}
}
Policy-Based Access Control
In policy-based ledgers, access control is managed through global policies instead of individual record access rules. This provides a centralized approach to access management.
Policy Structure
Policies with schema: 'access'
contain access rules that are evaluated globally:
/**
* Example access policy that grants wallet access to team members
*/
{
"handle": "team-wallet-access",
"schema": "access",
"record": "wallet",
"values": [
{
"action": "read",
"record": "wallet",
"bearer": {
"$signer": {
"$circle": "team-members"
}
}
},
{
"action": "update",
"record": "wallet",
"signer": {
"$circle": "team-leads"
}
}
]
}
Policy Status and Enforcement
Only active policies are enforced:
created
: Policy exists but is not enforcedactive
: Policy is enforced and controls accessinactive
: Policy is temporarily disabled
# Activate a policy to enforce it
minka policy activate team-wallet-access
# Deactivate a policy to temporarily disable it
minka policy deactivate team-wallet-access
Domain-Specific Policies
Policies can be applied globally or to specific domains:
/**
* Policy that applies only to records in the "payments" domain
*/
{
"handle": "payments-access@payments",
"schema": "access",
"record": "wallet",
"values": [
{
"action": "any",
"signer": {
"$circle": "payment-operators"
}
}
]
}
Configuring Access Strategy
Setting Access Strategy
The access strategy is configured in the ledger's configuration:
{
"config": {
"access.strategy": "record-based" // Default
// OR
"access.strategy": "policy-based" // Centralized policy management
}
}
See [How to migrate ledger access strategy](/ledger/how-to-guides/migrate-ledger-access-strategy) for more details.
Migration Considerations
One-way migration: Ledgers can be migrated from record-based
to policy-based
, but cannot be changed back. This prevents users from accidentally locking themselves out of the ledger.
When migrating to policy-based access:
- Plan your policies: Design your policy structure before migration to ensure you won't lock yourself out of ledger.
- Test thoroughly: Test the migration in a development environment
- Record rules ignored: All existing record-level access rules are ignored in favor of global policies.
Choosing the Right Strategy
Use Record-Based When:
- You need granular, per-record access control
- Different records require different access patterns
- You prefer decentralized access management
Use Policy-Based When:
- You want centralized access management
- You have complex, organization-wide access policies
- You require dynamic policy activation/deactivation
Related Documentation
- How to Migrate Ledger Access Strategy - Step-by-step guide to migrate from record-based to policy-based
- How to Activate Access Policy - Activate policies to enforce access control
- How to Deactivate Access Policy - Temporarily disable access policies
- About Security Policies - Detailed information about policy structure and management