Skip to main content

MSQ

Advanced
Authentication

Overview

MSQ is a MetaMask Snap designed to allow users to use Metamask to authenticate with dapps deployed on ICP. MSQ uses a scoped-identity architecture, meaning each user gets a unique and dedicated identity for each domain they interact with. Scoped-identity architectures help protect users from signature-stealing attacks.

I'll let you decide what is the best way to explain this!

Response from MSQ team: https://forum.dfinity.org/t/msq-hits-the-metamask-snap-store/30499/23

It provides two primary features:

  • User authorization: Web services can sign arbitrary data with a user's scoped key pair.

  • ICRC-1 payments: Users can request to perform ICRC-1 transfers to pay for goods or services.

Installation

Insall the msq package with the command:

npm install @fort-major/msq-client

Then, import it into your project's package.json file:

"dependencies": {
...
"@fort-major/msq-client": "^0.3",
...
}

Users will need to connect to MetaMask and the MSQ Snap.

The MSQ client library does this using the following function. For example, a user who interacts with a frontend element (e.g., button) that triggers the function will be prompted to connect using Metamask and install the MSQ Snap.

const result = await MsqClient.create();

if (!("Ok" in result)) {
// handle possible errors
}

Then, retrieve the client with the following code:

import { TMsqCreateOk, MsqClient } from "@fort-major/msq-client";

const msq: MsqClient = (result as TMsqCreateOk).Ok;

The MSQ client library version correlates with the respective Snap version. For example, the client library version 0.2.4 works with Snap version 0.2.x or 0.3.x, but does not work with 1.x.x.

User authorization

To enable users to authorize with MSQ and interact with your dapp, you can call the requestLogin() function of the client:

import { Identity } from "@dfinity/identity";

const identity: Identity | null = await msq.requestLogin();

if (identity === null) {
// the user has rejected to log in
}

This function returns an identity object which can then be supplied to an agent's HttpAgent constructor which can then be used to call canisters on a user's behalf:

import { HttpAgent } from "@dfinity/agent";

const agent = new HttpAgent({ identity });

Authorization for a user lasts indefinitely unless explicitly ended. The following code can be used to check if a user's session exists:

if (msq.isAuthorized()) {
}

To stop the user's session, use the requestLogout() function:

if (await msq.requestLogout()) {
} else {
}

You can learn more about user authentication in the MSQ documentation.

ICRC-1 payments

MSQ enables users to make payments using a pre-defined ICRC-1 account through the requestICRC1Payment() function:

const blockId: bigint | null = await msq.requestICRC1Payment({
if (blockId === null) {
renderErrorScreen("Payment failed");
},
// A canister ID of the valid `ICRC-1` token:
tokenCanisterId,
to: {
// Payment recipient's `principal` ID
owner,
// Payment recipient's `subaccount` ID
subaccount,
},
// An amount of tokens that the user needs to transfer to the recipient
amount,
// An optional ICRC-1 memo
memo,
});

This function returns either a bigint or null. A bigint response will indicate the block number of the executed ICRC-1 transaction and means that the payment was successful. That block number can then be used to query the token canister, verify the payment, and execute some business logic.

MSQ supports ICRC-1 token payments by default through a series of whitelisted ICRC-1 tokens. If you accept payment of a whitelisted token, your workflow will execute as expected. However, if you accept a token that isn't currently whitelisted, the user will be prompted to add the token to their personal whitelist.

View the MSQ documentation for the full list of whitelisted ICRC-1 tokens.

Resources