Access Control

Access control is the process of defining who has access to what actions. In Scrypto, access control rules not only define who can access component methods, they also define resource behavior. In this article, we describe the basic principles of badge-based access control.

Different from Ethereum, where access control is based on the caller address, in Scrypto authorization is granted in the form of badges. Any actor is allowed to take a privileged action if the proper authority to do so is present (more on this later). 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 any other fungible or non-fungible token. For example:

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

Proofs

One of the important conventions of badge usage is that, under normal usage, they are not actually withdrawn from a Vault and passed around. Instead, a Proof is created and used to prove that an actor has access to that badge.

You can create a Proof for a particular resource from a Vault or (rarely) a Bucket, and then do things with that Proof without changing ownership of the underlying contents. For example, if my account holds a FLIX token signifying that I am a member of Radflix, I can create a Proof of that token and present it to a Radflix component so that it will allow me to access it. I’m not actually transferring it…​even if the Radflix component was buggy or malicious it would have no ability to take control of the underlying token from which the Proof was generated. Think of it just like flashing a badge in the real world. Whoever you show it to can see that you possess it, and can inspect it, but you’re not actually handing it to them so they can’t hang on to it.

Proofs have a quantity associated with them, and a Proof can not be created with a quantity of 0. That is, if you have a Vault which is configured to hold a specified resource, but that Vault is empty, you can’t create a Proof of that resource. Scenarios where you care about the quantity associated with a Proof are unusual; typically you simply care whether it is present at all.

The Authorization Zone

The top level of every transaction, accessible by the transaction manifest, contains a worktop where resources are stored, and an authorization zone where Proofs are stored. When calling any Scrypto method from the manifest, the rules governing access to that method are automatically compared to the contents of the authorization zone. If the rules can be met, access to the method is granted and the call succeeds. If the rules can’t be met, then the transaction immediately aborts. There’s no need to specify what Proofs you think are necessary to meet the rules; the system just figures it out for you.

The same logic applies within a component called directly from the manifest. If it attempts a privileged action on a resource, such as trying to mint additional supply, the rules are checked against the contents of the authorization zone. If the rules can be met, the action succeeds. If the rules can’t be met, the transaction aborts.

That’s it. In the vast majority of use cases, you don’t have to think about access control or the authorization zone. The system just takes care of it.

Passing by Intent

There are times when you actually need your code to see what Proof a caller is using. For example, what if you issue a non-fungible badge to each of your system members, which contains some metadata like a user ID that your code will care about. In these cases, you add a Proof parameter to your method, and your caller can make a clone of a Proof sitting in the authorization zone to then pass it to the method just like any other parameter. We call this method of explicitly providing a Proof "passing by intent".

Here is an example where we display data contained in the NFT that the user passed a proof of:

pub fn query_ticket(&mut self, ticket: Proof) {
    // Make sure the provided proof is of the right resource address
    let validated_ticket = ticket.validate_proof(self.ticket_address).unwrap();

    // Get the data associated with the passed NFT proof
    let ticket_data: TicketData = validated_ticket.non_fungible().data();

    info!("You inserted {} XRD into this component", ticket_data.xrd_amount);
}

Before we get the proof’s data with non_fungible().data() we have to do a validation step. If we didn’t, people could send us any NFT that has the field xrd_amount and trick our components.

Validated Proofs Passed by Intent

It is essential that any proof passed by intent is validated to ensure that it is of a resource address expected by the component. Therefore, data on a Proof is not available prior to validation, only once a Proof has been validated can its contents and data be visible and usable. Proof validation is typically done against a resource address, however, a proof can also be validated to check that it contains the correct resource address, correct amount of a specific resource, or contains a specific non-fungible-id. Once the Proof has been converted to a ValidatedProof can the data on the proof be accessed and made usable.

The easiest way to validate a Proof is by validating the ResourceAddress of the proof. The following is an example of how that may be done:

let resource_address: ResourceAddress = self.user_badge_resource_address;
let user_badge: ValidatedProof = user_badge.validate_proof(resource_address)
    .expect("Invalid badge.");

The validation of the resource address is only one of the validation modes possible with Proofs. A Proof can be validated against any of five validation modes which are available through the ProofValidationMode enum. .

Validation Mode Name

Description

ValidateResourceAddress

Validates that the Proof contains a specific resource address.

ValidateResourceAddressBelongsTo

Validates that the resource address of the proof is one of a given list of addresses.

ValidateContainsNonFungible

Validates the proof contains a non-fungible with a specific NonFungibleAddress.

ValidateContainsNonFungibles

Validates the proof contains a non-fungible tokens of a given set of ids.

ValidateContainsAmount

Validates the proof contains a specific amount of a specific resource address.

The above given example can be improved by validating the Proof against a resource address and an amount to make sure that the caller is presenting a correct amount of their badge.

let resource_address: ResourceAddress = self.user_badge_resource_address;
let validation_mode: ProofValidationMode = ProofValidationMode::ValidateContainsAmount(resource_address, dec!("1"));
let user_badge: ValidatedProof = user_badge.validate_proof(validation_mode)
    .expect("Invalid badge.");

Once a proof has been validated, the data on the proof (such as resource address, amount, non fungible ids, etc…​) can be accessed and used.

Limitations on Proof Visibility

Because Proofs provide authorization to privileged methods or resource actions, it’s important to understand how they are allowed to move, who can see them, and how they are prevented from being abused by malicious or buggy components.

The short explanation is that Proofs can freely move "up" the callstack as many times as you like, but can only move "down" the stack once. That is, if your transaction manifest calls a method on ComponentAlpha which returns a Proof back to the authorization zone, a later call from your manifest to ComponentBravo will be able to utilize the Proof. But if ComponentBravo calls ComponentCharlie directly, that Proof won’t be considered when determining if Charlie’s method can be called, and won’t be considered for any privileged resource actions within Charlie’s method.

This same logic applies when you pass a Proof by intent, directly in the method parameters. The method you called can see and use the Proof for resource actions, but it can’t pass it on to another component, nor use it for accessing a privileged method.

While executing, any called component has its own local authorization zone which it can add or remove from. So if Bravo needs to call a method on Charlie, and needs a badge to do so, it will have to create a Proof of that badge and place it in its local authorization zone.

This sounds more complicated to explain than it is to practice. Stuff you call directly can utilize your authorization zone (either the transaction authorization zone, if calling directly from the manifest, or the local authorization zone of your component, if doing a component-to-component call). If the stuff you called wants to call some other component, then they’re on their own for authorization. In other words, if you pass a Proof of your user ID badge to some component, you can be confident that it’s not then going to be able to call something else and masquerade as you.

Next step: setting access rules

Looking for the syntax of rule setting and some examples? Please see the appropriate subsection here.