Canister Access Control

I have an app which consists of 5 canisters.

Problem

Most methods within those canisters are declared as public shared, because I need to use them from other canisters (frontend canister or even other canisters). But this means those methods are accessible by anyone outside the app, right? :warning:

Question

What are the recommendations for protecting access to methods of those canisters, considering that I will also need access to those methods from outside the app, from specific principals (for monitoring purposes) ?

I’ve browsed around the forum threads a bit, but haven’t found anything relevant. I’m probably not using the right keywords, because this seems like an obvious security concern.

Thanks!

Yes, anyone can access the functions. The best way to test if a function is completely public is to try it without authenticating to the Candid UI (the backend link provided) when you deploy (either locally or on the mainnet).

An initial thing is to require users to authenticate to access the function.

For example, in Rust:

let caller = ic_cdk::caller();
    if caller == Principal::anonymous() {
        panic!("Anonymous principal not allowed to make calls.")
    }
    caller

It sounds like you want to take a step even further by only allowing users who have authenticated on your frontend to access any backend functions. There isn’t a built-in way to do this.

Thanks ! Actually, I want to go even one step further: I want some methods to be usable only by a predetermined list of canisters (other canisters of my app + me). I guess I can manually check that the caller is among that list. But yes, my question was exactly this: is there any built-in way of doing this? (I use Motoko)

Yes, you would be able to do this.

You could create a hashmap of the list of canisters (an array would work too but I prefer hashmaps) and loop through it to check the caller is one of the principals in the hashmap.

2 Likes

What would be a proper way to wrap all methods with a principal check? :grimacing:

I implemented an access control mechanism based on access tokens in the ic-oss service.

This allows for fine-grained permission control over files, folders, buckets, cluster, including support for permission inheritance in the file tree. It is a highly flexible access control system designed to meet enterprise needs.
I plan to develop it into a standalone access control service that can be called by other canisters.

Source code for permission definitions:

Example source code for querying permissions:

1 Like

The high level approach is to return a Result rather than the direct type you want to return.

Then you can add a check of

if is_authorized(caller) {
  #err("Not Authorized);
}

at the start of each access controlled method.

You can use a library to abstract this, but it’s easy enough to check against a hardcoded or dynamic list.

Yes, I see. I’m already using Result on all my methods so it should be straight forward to add.

As for your last sentence, I agree, but it just feels like every single project building on canisters will want a way to protect their methods, no? So I’m a bit surprised that there’s no “built-in” way to do this already.

In case someone else is interested, I dug up this topic which appears to be doing exactly this in Motoko.