Creating and Linking Custom Permissions

EOSIO permissions control what an account is authorized to execute on an EOSIO blockchain. EOSIO permissions are powerful, flexible, and customizable. This tutorial explains custom permissions and illustrates how to use them. In the tutorial you will create a custom permission, link a permission to an action, unlink a permission from an action, and delete a custom permission.

Before You Begin

This tutorial requires the following:

What is a Custom Permission

A custom permission is an arbitrarily named permission created and associated with an EOSIO account. When an account is created two permissions are created by default; owner and active. You can create a new permission, a custom permission, as a child permission of owner, active or another custom permission. Custom permissions require a public and private key pair. Custom permissions may be linked to smart contract actions to specify the permission required to execute that action. EOSIO accounts and permissions enable flexible and highly granular control over accounts and smart contract actions.

The permission eosio.code is a special permission that allows smart contracts to call inline actions. The EOSIO blockchain performs an authorization check when an inline action is called from a smart contract. Smart contracts do not have access to account keys so cannot add authorization. Adding eosio.code to an account permission provides explicit permission for that account permission to execute an inline action. For information on how to add the eosio.code see cleos set account permission.

Why use a Custom Permission

Use custom permssions to improve the security on your account and for calling specific transactions. Use custom permissions to reduce the use of the owner and active keys and to limit the risk of the owner and active permissions being compromised.

Custom permissions always have a parent permission. The parent permission has the authority to execute the same transactions as the custom permission. The parent permission also has the authority to recover the custom permission keys if the related custom permission is compromised.

Using Custom Permissions

The following section explains in detail what you will do, what is required, and provides a simple smart contract you can use for testing.

The following examples illustrate how to:

You will use:

  • An account called bob, and the keys to control this account stored in a local wallet.
  • An account called scholder, and the keys to control this account stored in a local wallet.
  • A smart contract called hello which has been deployed to the scholder account. This smart contract has three actions:

    • what(eosio::name user)
    • why(eosio::name user)
    • how(eosio::name user)

You will create two custom permissions on the bob account:

  • A custom permission, customp1, whose parent is active
  • A custom permission, customp2, whose parent is customp1

You will link the custom permissions to smart contract actions:

  • Link customp1 to what(eosio::name user)
  • Link customp2 to how(eosio::name user)

You will see that the active permission can call all the actions. You will see that the customp1 permission can call the why and how actions. You will see that the customp2 permission can only call the how action.

You will then unlink and delete the customp2 permission.

The simple smart contract

The simple smart contract code used in the examples:

#include <eosio/eosio.hpp>
class [[eosio::contract]] hello : public eosio::contract {
  public:
      using eosio::contract::contract;
      [[eosio::action]] void what( eosio::name user ) {
         print( "hi, what do you want ", user);
      }

      [[eosio::action]] void why( eosio::name user ) {
         print( "why not ", user);
      }

      [[eosio::action]] void how( eosio::name user ) {
         print( "how are you ", user);
      }
};

Deploy this smart contract to your running blockchain.

This simple sample smart contract does not do any authorization checking, i.e. does not use require_auth. This tutorial demonstrates native authorization checking. Click on this link for more information on securing your contract.

Create Custom Permissions

To use a custom permission you need to create a custom permission.

Create custom permission customp1, with the parent active, on the bob account using the command cleos set account permission:

cleos set account permission bob customp1 EOS58wmANoBtT7RdPgMRCGDb37tcCQswfwVpj6NzC55D247tTMU9D active -p bob@active

The output should be similar to:

executed transaction: 97e2af6966b40ea0b523402110c6a5592862c5ad2abbaad20c9bbf2f68017c98  160 bytes  145 us
#         eosio <= eosio::updateauth            {"account":"bob","permission":"customp1","parent":"active","auth":{"threshold":1,"keys":[{"key":"EOS...

Now create custom permission customp2, with the parent customp1, on the bob account:

cleos set account permission bob customp2 EOS58wmANoBtT7RdPgMRCGDb37tcCQswfwVpj6NzC55D247tTMU9D customp1 -p bob@active

The output should be similar to:

executed transaction: 8b5e88d0d1cea6dd31a4967912d575d62391348345c58b6071aba7fb93d709b3  160 bytes  129 us
#         eosio <= eosio::updateauth            {"account":"bob","permission":"customp2","parent":"customp1","auth":{"threshold":1,"keys":[{"key":"E...

You can create a custom permission without specifying the parent, this will default to a parent of active:

cleos set account permission bob customp3 EOS58wmANoBtT7RdPgMRCGDb37tcCQswfwVpj6NzC55D247tTMU9D -p bob@active

The output should be similar to:

executed transaction: 42158f0a35bdce9e8374691285e12e5e517ab4d4831a68c8e3a2bb22e88fda7c  160 bytes  165 us
#         eosio <= eosio::updateauth            {"account":"bob","permission":"customp3","parent":"active","auth":{"threshold":1,"keys":[{"key":"EOS...

Let’s check the account:

cleos get account bob --json

The output will be similar to:

{
  "account_name": "bob",
  "head_block_num": 23485,
  "head_block_time": "2021-05-06T07:46:19.000",
  "privileged": false,
  "last_code_update": "1970-01-01T00:00:00.000",
  "created": "2021-05-06T05:07:27.500",
  "ram_quota": -1,
  "net_weight": -1,
  "cpu_weight": -1,
  "net_limit": {
    "used": -1,
    "available": -1,
    "max": -1
  },
  "cpu_limit": {
    "used": -1,
    "available": -1,
    "max": -1
  },
  "ram_usage": 4414,
  "permissions": [{
      "perm_name": "active",
      "parent": "owner",
      "required_auth": {
        "threshold": 1,
        "keys": [{
            "key": "EOS58wmANoBtT7RdPgMRCGDb37tcCQswfwVpj6NzC55D247tTMU9D",
            "weight": 1
          }
        ],
        "accounts": [],
        "waits": []
      }
    },{
      "perm_name": "customp1",
      "parent": "active",
      "required_auth": {
        "threshold": 1,
        "keys": [{
            "key": "EOS58wmANoBtT7RdPgMRCGDb37tcCQswfwVpj6NzC55D247tTMU9D",
            "weight": 1
          }
        ],
        "accounts": [],
        "waits": []
      }
    },{
      "perm_name": "customp2",
      "parent": "customp1",
      "required_auth": {
        "threshold": 1,
        "keys": [{
            "key": "EOS58wmANoBtT7RdPgMRCGDb37tcCQswfwVpj6NzC55D247tTMU9D",
            "weight": 1
          }
        ],
        "accounts": [],
        "waits": []
      }
    },,{
      "perm_name": "customp3",
      "parent": "active",
      "required_auth": {
        "threshold": 1,
        "keys": [{
            "key": "EOS58wmANoBtT7RdPgMRCGDb37tcCQswfwVpj6NzC55D247tTMU9D",
            "weight": 1
          }
        ],
        "accounts": [],
        "waits": []
      }
    },{
      "perm_name": "owner",
      "parent": "",
      "required_auth": {
        "threshold": 1,
        "keys": [{
            "key": "EOS58wmANoBtT7RdPgMRCGDb37tcCQswfwVpj6NzC55D247tTMU9D",
            "weight": 1
          }
        ],
        "accounts": [],
        "waits": []
      }
    }
  ],
  "total_resources": null,
  "self_delegated_bandwidth": null,
  "refund_request": null,
  "voter_info": null,
  "rex_info": null
}

For simplicity we are using the same public/private key pair for each permission. For production systems we recommend that you create and use new key pairs for each permission.

Once you have a custom permission you can link this custom permission to a smart contract action, requiring that permission level authorization, or higher, to execute the action.

Link the custom permission customp1, to the what action using the command cleos set action permission:

cleos set action permission bob scholder what customp1 -p bob@active

The output should be similar to:

executed transaction: 64198d1cc5f7dedf8809b86f22801eb004d50365ba72f8e2833ed191c6f6e30b  128 bytes  471 us
#         eosio <= eosio::linkauth              {"account":"bob","code":"scholder","type":"what","requirement":"customp1"}

Link the custom permission customp2, to the how action using the command cleos set action permission (link)`:

cleos set action permission bob scholder how customp2 -p bob@active

The output should be similar to:

executed transaction: 64198d1cc5f7dedf8809b86f22801eb004d50365ba72f8e2833ed191c6f6e30b  128 bytes  471 us
#         eosio <= eosio::linkauth              {"account":"bob","code":"scholder","type":"how","requirement":"customp2"}

Test It

We now have linked two custom permissions, customp1, and customp2, to two actions, what, and how.

  • active is able to call why, what, and how. The permission active is the parent of customp1 so is able to call anything that customp1 can call.
  • customp1 is able to call what, and how. The permission customp1 is the parent of customp2 so is able to call anything that customp2 can call.
  • customp2 is able to call how

We can test this by using the permissions to call the smart contract actions.

Call the actions using the active permission

Call why with bob@active:

cleos push action scholder why '["name"]' -p bob@active

The output should be similar to:

executed transaction: 43b6ad4ce7a52d7281ccd2800caa02d5278ee714de36fefe9624bff621378402  104 bytes  129 us
#      scholder <= scholder::why               {"user":"name"}
>> why not name

Call what with bob@active:

cleos push action scholder what '["name"]' -p bob@active

The output should be similar to:

executed transaction: 43b6ad4ce7a52d7281ccd2800caa02d5278ee714de36fefe9624bff621378402  104 bytes  129 us
#      scholder <= scholder::what               {"user":"name"}
>> hi, what do you want name

Call how with bob@active:

cleos push action scholder how '["name"]' -p bob@active

The output should be similar to:

executed transaction: 43b6ad4ce7a52d7281ccd2800caa02d5278ee714de36fefe9624bff621378402  104 bytes  129 us
#      scholder <= scholder::how               {"user":"name"}
>> how are you name

We see that the active permission can sucessfully call all the actions.

Call the actions using the customp1 permission

Call why with bob@customp1:

cleos push action scholder why '["name"]' -p bob@customp1

The output should be similar to:

Error 3090005: Irrelevant authority included
Please remove the unnecessary authority from your action!
Error Details:
action declares irrelevant authority '{"actor":"bob","permission":"customp1"}'; minimum authority is {"actor":"bob","permission":"active"}

Call what with bob@customp1:

cleos push action scholder what '["name"]' -p bob@customp1

The output should be similar to:

executed transaction: 43b6ad4ce7a52d7281ccd2800caa02d5278ee714de36fefe9624bff621378402  104 bytes  129 us
#      scholder <= scholder::what               {"user":"name"}
>> why not name

Call how with bob@customp1:

cleos push action scholder how '["name"]' -p bob@customp1

The output should be similar to:

executed transaction: 43b6ad4ce7a52d7281ccd2800caa02d5278ee714de36fefe9624bff621378402  104 bytes  129 us
#      scholder <= scholder::how               {"user":"name"}
>> how are you name

We see that the customp1 permission can sucessfully call the what and how actions, but fails to call the why action.

Call the actions using the customp2 permission

Call why with bob@customp2:

cleos push action scholder why '["name"]' -p bob@customp2

The output should be similar to:

Error 3090005: Irrelevant authority included
Please remove the unnecessary authority from your action!
Error Details:
action declares irrelevant authority '{"actor":"bob","permission":"customp2"}'; minimum authority is {"actor":"bob","permission":"active"}

Call what with bob@customp2:

cleos push action scholder what '["name"]' -p bob@customp2

The output should be similar to:

Error 3090005: Irrelevant authority included
Please remove the unnecessary authority from your action!
Error Details:
action declares irrelevant authority '{"actor":"bob","permission":"customp2"}'; minimum authority is {"actor":"bob","permission":"customp1"}

Call how with bob@customp2:

cleos push action scholder how '["name"]' -p bob@customp2

The output should be similar to:

executed transaction: 43b6ad4ce7a52d7281ccd2800caa02d5278ee714de36fefe9624bff621378402  104 bytes  129 us
#      scholder <= scholder::how               {"user":"name"}
>> how are you name

We see that the customp2 permission can sucessfully call the how action, but fails to call the why and what actions.

You can unlink permissions to a smart contract action.

Now we will unlink the customp2 permission from the how action using the command cleos set action permission:

cleos set action permission bob scholder how NULL -p bob@active

The output should be similar to:

executed transaction: 50fe754760a1b8bd0e56f57570290a3f5daa509c090deb54c81a721ee7048201  120 bytes  242 us
#         eosio <= eosio::unlinkauth            {"account":"bob","code":"scholder","type":"how"}

Test It Again

We have unlinked the customp2 permission.

We now have one linked custom permission, customp1 which can call what. The customp2 permission is now unlinked so should not be able to call anything.

  • active is able to call why, what, and how. The permission active is the parent of customp1 so is able to call anything that customp1 can call.
  • customp1 is able to call what.
  • customp2 is not linked to any action so should be unable to call an action.

Call the how action using the all the permissions to show what permission has the authority to call the action

Call how with bob@active:

cleos push action scholder how '["name"]' -p bob@active

The output should be similar to:

executed transaction: 43b6ad4ce7a52d7281ccd2800caa02d5278ee714de36fefe9624bff621378402  104 bytes  129 us
#      scholder <= scholder::how               {"user":"name"}
>> how are you name

Call how with bob@customp1:

cleos push action scholder how '["name"]' -p bob@customp1

The output should be similar to:

Error 3090005: Irrelevant authority included
Please remove the unnecessary authority from your action!
Error Details:
action declares irrelevant authority '{"actor":"bob","permission":"customp1"}'; minimum authority is {"actor":"bob","permission":"active"}

Call how with bob@customp2:

cleos push action scholder how '["name"]' -p bob@customp2

The output should be similar to:

Error 3090005: Irrelevant authority included
Please remove the unnecessary authority from your action!
Error Details:
action declares irrelevant authority '{"actor":"bob","permission":"customp2"}'; minimum authority is {"actor":"bob","permission":"active"}

Delete Custom Permissions

Now you have unlinked the customp2 permission you can delete this permission using the command cleos set account permission:

cleos set account permission bob customp2 NULL active -p bob@active

The output should be similar to:

executed transaction: 3f3e58707e5548ec34f5655327b1110c18d455c9ee0a6cffc102d7bc4e0a6cdb  112 bytes  472 us
#         eosio <= eosio::deleteauth            {"account":"bob","permission":"customp2"}

Let’s check the account:

cleos get account bob --json

The output will be similar to:

{
  "account_name": "bob",
  "head_block_num": 23485,
  "head_block_time": "2021-05-06T07:46:19.000",
  "privileged": false,
  "last_code_update": "1970-01-01T00:00:00.000",
  "created": "2021-05-06T05:07:27.500",
  "ram_quota": -1,
  "net_weight": -1,
  "cpu_weight": -1,
  "net_limit": {
    "used": -1,
    "available": -1,
    "max": -1
  },
  "cpu_limit": {
    "used": -1,
    "available": -1,
    "max": -1
  },
  "ram_usage": 4414,
  "permissions": [{
      "perm_name": "active",
      "parent": "owner",
      "required_auth": {
        "threshold": 1,
        "keys": [{
            "key": "EOS58wmANoBtT7RdPgMRCGDb37tcCQswfwVpj6NzC55D247tTMU9D",
            "weight": 1
          }
        ],
        "accounts": [],
        "waits": []
      }
    },{
      "perm_name": "customp1",
      "parent": "active",
      "required_auth": {
        "threshold": 1,
        "keys": [{
            "key": "EOS58wmANoBtT7RdPgMRCGDb37tcCQswfwVpj6NzC55D247tTMU9D",
            "weight": 1
          }
        ],
        "accounts": [],
        "waits": []
      }
    },{
      "perm_name": "customp3",
      "parent": "active",
      "required_auth": {
        "threshold": 1,
        "keys": [{
            "key": "EOS58wmANoBtT7RdPgMRCGDb37tcCQswfwVpj6NzC55D247tTMU9D",
            "weight": 1
          }
        ],
        "accounts": [],
        "waits": []
      }
    },{
      "perm_name": "owner",
      "parent": "",
      "required_auth": {
        "threshold": 1,
        "keys": [{
            "key": "EOS58wmANoBtT7RdPgMRCGDb37tcCQswfwVpj6NzC55D247tTMU9D",
            "weight": 1
          }
        ],
        "accounts": [],
        "waits": []
      }
    }
  ],
  "total_resources": null,
  "self_delegated_bandwidth": null,
  "refund_request": null,
  "voter_info": null,
  "rex_info": null
}

Conclusion

This tutorial demonstrates how to create, link, and delete custom permissions. The tutorial shows you how to use these permissions to control which actions an account permission can perform.

Further Examples

Custom permissions can also be controlled directly using the JavaScript SDK. For examples follow these links:

What's Next

  • Payable Actions: Learn how to write a smart contract that has payable actions.