Explanations

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.

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 manage symbol within a wallet 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:

  1. user A wants to read a symbol , so firstly it must fulfill requirements for accessing the server and then fulfill requirements to access the ledger. Only after those requirements are satisfied, the ledger will check if the user A can perform the read operation on the symbol.

  2. user B wants to create a ledger , so it should have permissions to access the server first. After that the ledger will check if the user has permissions to create a ledger.

Policy-Based Access Flow

In policy-based ledgers, access control is simplified:

server → active policies (global evaluation)

The system:

  1. Checks server-level access constraints
  2. Evaluates all active policies with schema: 'access' globally
  3. Ignores access rules defined on individual records
  4. 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 enforced
  • active: Policy is enforced and controls access
  • inactive: 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:

  1. Plan your policies: Design your policy structure before migration to ensure you won't lock yourself out of ledger.
  2. Test thoroughly: Test the migration in a development environment
  3. 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