Principals and Authentication
Principals are unique identifiers for either canisters or clients (external users). Both a canister or an external user can use a principal to authenticate themselves on the Internet Computer.
Principals in Motoko are a primitive type called Principal
. We can work directly with this type or use an alternative textual representation of a principal.
Here's an example of the textual representation of a principal:
let principal : Text = "k5b7g-kwhqt-xj6wm-rcqej-uwwp3-t2cf7-6banv-o3i66-q7dy7-pvbof-dae";
The variable principal
is of type Text
and has a textual value of a principal.
Anonymous principal
There is one special principal called the anonymous principal. It used to authenticate to the Internet Computer anonymously.
let anonymous_principal : Text = "2vxsx-fae";
We will use this principal later in this chapter.
The Principal Type
To convert a textual principal into a value of type Principal
we can use the Principal module.
import P "mo:base/Principal";
let principal : Principal = P.fromText("k5b7g-kwhqt-xj6wm-rcqej-uwwp3-t2cf7-6banv-o3i66-q7dy7-pvbof-dae");
We import the Principal module from the Base Library and name it P
. We then defined a variable named principal
of type Principal
and assigned a value using the .fromText()
method available in the Principal module.
We could now use our principal
variable wherever a value is expected of type Principal
.
Caller Authenticating Public Shared Functions
There is a special message object that is available to public shared functions. Today (Jan 2023) it is only used to authenticate the caller of a function. In the future it may have other uses as well.
This message object has the following type:
type MessageObject = {
caller : Principal;
};
We chose the name MessageObject
arbitrarily, but the type { caller : Principal }
is a special object type available to public shared functions inside actors.
To use this object, we must pattern match on it in the function signature:
public shared(messageObject) func whoami() : async Principal {
let { caller } = messageObject;
caller;
};
Our public shared update function now specifies a variable name messageObject
(enclosed in parenthesis ()
) in its signature after the shared
keyword. We now named the special message object messageObject
by pattern matching.
In the function body we pattern match again on the caller
field of the object, thereby extracting the field name caller
and making it available in the function body. The variable caller
is of type Principal
and is treated as the return value for the function.
The function still has the same function type regardless of the message object in the function signature:
type WhoAmI = shared () -> async Principal;
We did not have to pattern match inside the function body. A simple way to access the caller field of the message object is like this:
public shared query (msg) func call() : async Principal {
msg.caller;
};
This time we used a public shared query function that returns the principal obtained from the message object.
Checking the Identity of the Caller
If an actor specifies public shared functions and is deployed, then anyone can call its publicly available functions. It is useful to know whether a caller is anonymous or authenticated.
Here's an actor with a public shared query function which checks whether its caller is anonymous:
import P "mo:base/Principal";
actor {
public shared query ({ caller = id }) func isAnonymous() : async Bool {
let anon = P.fromText("2vxsx-fae");
if (id == anon) true else false;
};
};
We now used pattern matching in the function signature to rename the caller
field of the message object to id
. We also declared a variable anon
of type Principal
and set it to the anonymous principal.
The function checks whether the calling principal id
is equal to the anonymous principal anon
.
Later in this book we will learn about message inspection where we can inspect all incoming messages before calling a function.