Scoped Authorization Grant

Scoped Authorization Grant is resource-scoped authorization for Daml. An authority issues an AuthorizationGrant that lets one party perform one operation on one resource. A protected choice checks the grant against a requirement that it derives from its own trusted state. Grants can have a validity window, and the authority can revoke them or the grantee can renounce them.

Role-based access control is built on top of the grants: each role maps to one permission, as the treasury example shows. The closest Solidity analogue is AccessControl, without a role hierarchy.

Pre-release and unaudited. The package has no release yet. Source: packages/access/scoped-authorization-grant-v1 in OpenZeppelin/canton-contracts.

PackageModuleDependencies
openzeppelin-scoped-authorization-grant-v1OpenZeppelin.ScopedAuthorizationGrantV1daml-prim, daml-stdlib
import DA.Functor (void)
import qualified OpenZeppelin.ScopedAuthorizationGrantV1 as SAG

Types

TypePurpose
AuthorizationScopeThe exact resource and permission: namespace, resourceType, resourceId, permission, policyEpoch (nonnegative), and an optional resourceInstanceCid.
AuthorizationGrantThe grant contract: authority, grantee, scope, and the optional validFrom (inclusive) and validUntil (exclusive) ledger-time bounds.
AuthorizationWhat the caller presents to a protected choice: actor and grantCid.
AuthorizationRequirementWhat the protected choice expects: expectedAuthority and expectedScope.
HasAuthorizationPolicyA type class that maps a resource, its contract ID, and an application permission to a requirement.

The Grant

AuthorizationGrant has signatory authority and observer grantee. Creation requires a nonnegative policyEpoch and validFrom < validUntil when both bounds are set.

ChoiceControllerConsumingEffect
AuthorizationGrant_UsegranteeNoChecks the validity window and records a use.
AuthorizationGrant_RevokeauthorityYesArchives the grant.
AuthorizationGrant_RenouncegranteeYesArchives the grant.

Expiry does not archive a grant. A later use of a revoked or renounced grant fails.

Guarding a Choice

Make authorization.actor the controller, derive the requirement from the contract state, and call the guard before the protected work:

nonconsuming choice ProtectedOperation : ()
  with authorization : SAG.Authorization
  controller authorization.actor
  do
    let requirement = SAG.AuthorizationRequirement with
          expectedAuthority = administrator
          expectedScope = scopeFromThisContract
    void $ SAG.requireAuthorization authorization requirement
    -- protected effects

requireAuthorization fetches the grant, checks the authority, the grantee, and the scope, then exercises AuthorizationGrant_Use. It returns the grant.

Never take the expected authority, resource, permission, or epoch from choice arguments. A caller that supplies the requirement chooses the policy that it is checked against.

Typed Policies

HasAuthorizationPolicy keeps issuance and guarding on the same requirement. Define a permission type and an instance next to the resource template:

instance SAG.HasAuthorizationPolicy LicenseRegistry Policy.LicensingPermission where
  authorizationRequirement registry cid Policy.IssueLicense = SAG.AuthorizationRequirement with
    expectedAuthority = registry.licensor
    expectedScope = Policy.issueLicenseScope cid registry.registryId registry.policyEpoch

The protected choice then calls void $ SAG.requirePermission this self Policy.IssueLicense authorization. Pass this and self, or fetched state with the contract ID used to fetch it, and select the permission in code.

Functions

FunctionRecords useResult
requireAuthorizationYesThe grant, or the transaction fails.
requirePermissionYesAs requireAuthorization, with the requirement from HasAuthorizationPolicy.
checkAuthorizationNoRight grant or Left FailureStatus with the first mismatch.
checkPermissionNoAs checkAuthorization, with the requirement from HasAuthorizationPolicy.
requireAuthorityOrGrantWhen a grant is presentedNone when the actor is the expected authority and presents no grant; otherwise the result of requireAuthorization.

Use a check function to choose between candidate grants, then call the matching require function on the selected grant. An archived or unavailable grant aborts on fetch; the check functions do not return it as Left.

requireAuthorityOrGrant suits an interface choice that receives the grant through an extensible context, such as the Splice ChoiceContext. Every implementation of such a choice must call a guard.

Scope and Resource Identity

Every scope field must match exactly.

  • resourceInstanceCid = None matches by logical fields only, so a grant applies to successor contracts.
  • resourceInstanceCid = Some cid binds the grant to one contract ID. The guard compares IDs; it does not fetch the resource.
  • To invalidate grants, increase the expected policyEpoch in the resource state. Do not reuse an earlier epoch: that reactivates old grants.

Failure Statuses

The require functions fail with DA.Fail.failWithStatus, and the check functions return the same status as Left. All statuses use FAILED_PRECONDITION. Match on the errorId, not on the message.

errorIdMetadata
openzeppelin.com/scoped-authorization-grant-authority-mismatchexpected, actual
openzeppelin.com/scoped-authorization-grant-grantee-mismatchexpected, actual
openzeppelin.com/scoped-authorization-grant-scope-mismatchfields
openzeppelin.com/scoped-authorization-grant-not-yet-validvalidFrom
openzeppelin.com/scoped-authorization-grant-expiredvalidUntil
openzeppelin.com/scoped-authorization-grant-requiredexpected, actual

The checks run in the order of the table and stop at the first mismatch. grant-required comes only from requireAuthorityOrGrant.

Security Considerations

  • Bind the actor. Authorization is input, not proof. The controller clause supplies the actor's authority, and the guard matches the grant's grantee to it.
  • Committed path. Keep the guard on the committed execution path. A caught exception can roll back a successful check.
  • Ledger time. The validity bounds use ledger time, which can differ from record time within the synchronizer tolerance. Allow a margin for real-world deadlines.
  • Duplicates. Each grant has its own contract ID and can authorize use. To offboard a grantee, revoke every applicable grant or advance the policy epoch.
  • Usage records. A direct AuthorizationGrant_Use exercise does not check the authority or the scope. Treat a use event as proof only when it comes from your guarded choice.
  • Authority. The authority can create grants directly, so it is trusted for every scope that it controls.

Examples

Build and Consume

From the root of canton-contracts:

DAML_PACKAGE=packages/access/scoped-authorization-grant-v1 dpm build
data-dependencies:
  - ../canton-contracts/packages/access/scoped-authorization-grant-v1/.daml/dist/openzeppelin-scoped-authorization-grant-v1-0.1.0.dar