Skip to main content

User Verification

Authentication on Tezos

To verify a user owns a Tezos address, it is crucial to send an expression request and then verify the signature on the backend. With kukai-embed, the expression is signed seamlessly in the background, eliminating the necessity for direct user input.

tip

The expression signed by the user adheres to a specific signing standard. For more details on the expression format, refer to the Message Signing page.

Signature Verification Process

  1. Generate a random challenge string (nonce) and a unique requestId on the back-end. This will be included in the expression that will be signed by the user:
Back-end
const requestId = "<request_id>"; // optional, system-specific identifier that may be used to uniquely refer to the sign-in request
const nonce = crypto.randomBytes(16).toString("base64");
  1. On the frontend, use either kukai-embed's login (with authParams) or authenticate to get a signature from the end user. You will need the nonce (challenge string) and the request_id (you may skip this by providing an empty string) that you created earlier on the back-end.
Front-end
// In one step:
const { authResponse, pk, pkh } = await kukaiEmbed.login({
authParams: { id: "<request_id>", nonce: "<nonce>" },
});

// In two steps:
const { pk, pkh } = await kukaiEmbed.login();
const authResponse = await kukaiEmbed.authenticate("<request_id>", "<nonce>");
Signed Message

When using login with authParams or authenticate, kukai-embed generates and signs a message that includes your request_id and challenge (nonce), as well as other information that might be useful for the authentication process on the back-end.

Example of a signed message
// used in this example: authenticate("<request_id>", "<nonce>")

Tezos Signed Message: {
"currentTime": "1704063600", // in seconds
"domain": "<your_domain>",
"network": "ghostnet | mainnet",
"nonce": "<nonce>",
"publicKey":"<public key>",
"address":"<tezos address>",
"purpose":"authentication",
"requestId":"<request_id>",
}
  1. Send the following to the backend:
  • message and signature (included in authResponse)
  • publicKey (pk)
  1. Verify the signature on the backend using @taquito/utils
Back-end
import { verifySignature } from "@taquito/utils";
import { toHex } from "./utils.ts";

const isNonceIncluded = message.includes("<nonce>");
// optional: verify that the request ID matches what you expect or that the timestamp falls within an acceptable time range

if (!isNonceIncluded) {
throw new Error("Expression mismatch");
}

const isVerified = verifySignature(toHex(message), publicKey, signature);
./utils.ts
export function toHex(message: string): string {
const input = Buffer.from(message);

const prefix = Buffer.from("0501", "hex");
const len_bytes = Buffer.from(
message.length.toString(16).padStart(8, "0"),
"hex"
);
const value = Buffer.concat(
[prefix, len_bytes, input],
prefix.length + len_bytes.length + input.length
);

return value.toString("hex");
}