Differentiation between the methods of obtaining the userPrincipal calling the function

Hello!

I am currently creating a backend canister in Rust.

I am reading code from dfinity/docs, dfinity/examples, and other applications to learn how to implement it, but I have a question about how to get the user Principal to call a function.

I have found two main ways depending on the program.

  1. Pass it as an argument
  2. Get it with ic_cdk::api::caller()

[ example ]

// 1. Argument
test_getter(caller: Principal)

// 2. Call ic_cdk::api::caller()
test_getter() {
    let caller = caller();
    // ...
}

How are these methods used differently?
Is there a standard or rough base idea for using them differently?
Thank you in advance for your answer.

Well, it depends on what you want to do with your functions. Supposing both of them are queries or updates, your example would go like this:

In this example, the caller gets to control the principal sent here. You, as a canister, have 0 control over it. Whatever the user sends you will receive here. This is obviously not OK if you want to check access rights for example.

You’d use this if you want to allow a principal to produce some effects on another principal, but in that case you’d probably want to call it something else just so you don’t confuse the two.

This is the proper way to get the true caller of a function. This is guaranteed by the replica to be the principal calling your function. You’d use this to verify that the user is who they say they are, implement access control, etc.

One quick gotcha on this subject:

If you have a call that does some async stuff (e.g. call an update fn on a different canister), you won’t be able to get the caller(); after the async is answered, so you’d probably want to save that in a local variable at the beginning of the function call, before issuing the async call.

2 Likes

Thanks for your response!

I did not know about this one, so I am learning a lot about it. Thank you!

Am I correct in understanding that, for example, if we assume that the user using the application is authenticated by Internet Identity, it is best to retrieve it with caller() rather than having the front end specify the Principal as an argument?

(e.g. dfinity/examples/Encrypted Notes dapp)

Yes, that’s right. Otherwise someone could modify the frontend to impersonate anybody.

Here’s an example how we use both approaches in the same code base:

  • Use ic_cdk::api::caller() to get the real caller (e.g. here)
  • Pass it into a function (that’s fn my_func(caller: Principal), e.g. here) so that testing becomes easier. Tests are here, but none use different callers right now I think.
1 Like

Thanks for sharing the reference code!

If you don’t pass in arguments, you can’t do Rust unit test casually and have a bit of trouble testing.
I think the current best way to test the functionality at once is to write out the dfx command in a shell script and run it.

Thank you very much.