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.
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);
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.
If you started Scrypto prior to v0.4, you’re accustomed to passing Proofs around directly to where they were needed (they were called BucketRefs and VaultRefs previously). This pattern has changed, and Proofs are now stored in the authorization zone.
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 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.
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 call, and your caller can make a clone of the Proof sitting in the authorization zone, giving it a name just like creating a Bucket from the worktop. Then they pass it in just like any other parameter.
We call this method of explicitly providing a Proof "passing by intent."
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.