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.