Access Control

Access control is the process of defining which user has access to what. In this article, we describe how you can use badge-based access control to secure your component methods.

Different from Ethereum, where access control is based on the caller address, in Scrypto authorization is granted in the form of badges. Anyone (whether a user’s account, or another component) is allowed to call a method if they are in possession of a badge that is authorized to do so. This gives us much more flexibility in defining security rules.

What is a Badge?

A badge isn’t a primitive type – it is a way of referring to a resource that is used mainly for authorization. A badge can be either a fungible or non-fungible resource, depending on your use case. For example, you need a non-fungible badge if you want to associate data to an individual badge unit. Or a fungible badge may be appropriate to define an authorization "role" that is provided to many users/components.

To create a new badge type, we use the ResourceBuilder just as we would to create a type of token or NFT. For example:

// Using `DIVISIBILITY_NONE` to make sure nobody can divide a badge into two parts.
let badge: Bucket = ResourceBuilder::new_fungible(DIVISIBILITY_NONE)
    .metadata("name", "admin badge")
    .initial_supply_fungible(1);

By convention,

  • An account can hold multiple badge types (in multiple vaults)

  • A badge type can be held by multiple accounts (if there are multiple units of the badge type)

  • Multiple badge types can be assigned to an authorization rule (access to a method)

  • A badge type can be assigned to multiple authorization rules

Defining Authorization Rules

Each method may define its own authorization rule that sets what badge holder may access it. We use the auth macro, which accepts an array of component state variables (each of which must be of type ResourceDef or Address).

A caller can access the method as long as any of these badges are presented.

Additional types of rules other than "any of these badges" may be added in the future, but the "any" rule type provides the greatest flexibility to start and covers the majority of typical use cases.

For example:

use scrypto::prelude::*;

blueprint! {
    struct Counter {
        admin: Address,
        count: u32,
    }

    impl Counter {
        pub fn new() -> (Component, Bucket) {
            let badge: Bucket = ResourceBuilder::new_fungible(DIVISIBILITY_NONE)
                .metadata("name", "admin badge")
                .initial_supply_fungible(1);

            // Instantiate a Hello component, populating its vault with our supply of 1000 HelloToken
            let component = Self {
                admin: badge.resource_address(),
                count: 0
            }
            .instantiate();

            // Return the badge to the caller
            (component, badge)
        }

        pub fn increase(&mut self) {
            self.count += 1;
        }

        #[auth(admin)]
        pub fn reset(&mut self) {
            self.count = 0;
        }
    }
}

In the above example, the reset() method at the bottom can only be called by anyone holding the admin badge, which is created when the component is instantiated in the new() function.

Calling a "Protected" Method

To call a method that is protected by a badge, we need to pass a BucketRef that refers to a bucket containing the badge.

let counter: Counter = Counter::from(counter_component_address);
let admin_badge: Bucket = self.admin_badge_vault.take(1);
counter.reset(admin_badge.present());

In the above example,

  1. The admin_badge is first taken from the admin_badge_vault.

  2. A BucketRef is created by calling the present() method on the admin_badge bucket.

  3. The bucket ref is passed to the reset method.

Only the BucketRef is passed as an argument and the original bucket is still owned by the caller.

Authorization Delegation

By default, the auth macro will drop the BucketRef immediately after authorization check. However, in certain use cases, you may want to keep the BucketRef and use it for another operation.

To do so, you will need to pass optional flag keep_auth to the auth macro. With this flag on, the input BucketRef can be referenced as auth to do other actions requiring that same badge for authorization.

#[auth(admin, keep_auth)]
pub fn disable_minting(&mut self) {
    self.count = 0;
    self.some_resource_def.disable_flags(MINTABLE, auth);
}

In some cases, you may also duplicate the bucket refs using auth.clone().

All input BucketRef must be dropped or returned to caller; otherwise, a runtime error is raised.