UNPKG

@biconomy/passkey

Version:

Passkey validator plug in for Biconomy SDK

157 lines 6.4 kB
import { startAuthentication, startRegistration } from "@simplewebauthn/browser"; import { concatHex, keccak256, pad, toHex } from "viem"; import { DEFAULT_PASSKEY_SERVER_URL } from "./constants.js"; import { b64ToBytes, uint8ArrayToHexString } from "./utils.js"; export var WebAuthnMode; (function (WebAuthnMode) { WebAuthnMode["Register"] = "register"; WebAuthnMode["Login"] = "login"; })(WebAuthnMode || (WebAuthnMode = {})); /** * Encodes a WebAuthn public key into a concatenated hex string. * The resulting hex string contains the X coordinate (32 bytes), Y coordinate (32 bytes), * and the authenticator ID hash (32 bytes) of the WebAuthn key. * * @param pubKey - The WebAuthn key object containing public key coordinates and authenticator details * @returns A hex string of the concatenated key components */ export const encodeWebAuthnPubKey = (pubKey) => { return concatHex([ toHex(pubKey.pubX, { size: 32 }), toHex(pubKey.pubY, { size: 32 }), pad(pubKey.authenticatorIdHash, { size: 32 }) ]); }; /** * Creates or retrieves a WebAuthn key for authentication or registration. * This function handles both registration of new passkeys and authentication with existing ones. * * @param params - WebAuthn account parameters * @param params.passkeyName - Name identifier for the passkey (required for registration) * @param params.rpID - Relying Project ID (domain name) * @param params.webAuthnKey - Existing WebAuthn key (if available) * @param params.mode - Authentication mode ('register' or 'login') * @param params.credentials - Request credentials mode ('include', 'omit', or 'same-origin') * @param params.passkeyServerHeaders - Additional headers for passkey server requests * * @returns Promise resolving to a WebAuthnKey object containing: * - pubX: X coordinate of the public key * - pubY: Y coordinate of the public key * - authenticatorId: Original authenticator ID * - authenticatorIdHash: Keccak256 hash of the authenticator ID * * @throws Error if registration/login verification fails or if required key data is missing * * @example * // Registration * const key = await toWebAuthnKey({ * passkeyName: "my-passkey", * mode: WebAuthnMode.Register * }); * * // Login * const key = await toWebAuthnKey({ * mode: WebAuthnMode.Login * }); */ export const toWebAuthnKey = async ({ passkeyName, rpID, webAuthnKey, mode = WebAuthnMode.Register, credentials = "include", passkeyServerHeaders = {} }) => { if (webAuthnKey) { return webAuthnKey; } let pubKey; let authenticatorId; if (mode === WebAuthnMode.Login) { // Get login options const loginOptionsResponse = await fetch(`${DEFAULT_PASSKEY_SERVER_URL}/login/options`, { method: "POST", headers: { "Content-Type": "application/json", ...passkeyServerHeaders }, body: JSON.stringify({ rpID }), credentials }); const loginOptions = await loginOptionsResponse.json(); // Start authentication (login) const loginCred = await startAuthentication(loginOptions); authenticatorId = loginCred.id; // Verify authentication const loginVerifyResponse = await fetch(`${DEFAULT_PASSKEY_SERVER_URL}/login/verify`, { method: "POST", headers: { "Content-Type": "application/json", ...passkeyServerHeaders }, body: JSON.stringify({ cred: loginCred, rpID }), credentials }); const loginVerifyResult = await loginVerifyResponse.json(); if (!loginVerifyResult.verification.verified) { throw new Error("Login not verified"); } // Import the key pubKey = loginVerifyResult.pubkey; // Uint8Array pubkey } else { // Get registration options const registerOptionsResponse = await fetch(`${DEFAULT_PASSKEY_SERVER_URL}/register/options`, { method: "POST", headers: { "Content-Type": "application/json", ...passkeyServerHeaders }, body: JSON.stringify({ username: passkeyName, rpID }), credentials }); const registerOptions = await registerOptionsResponse.json(); // Start registration const registerCred = await startRegistration(registerOptions.options); authenticatorId = registerCred.id; // Verify registration const registerVerifyResponse = await fetch(`${DEFAULT_PASSKEY_SERVER_URL}/register/verify`, { method: "POST", headers: { "Content-Type": "application/json", ...passkeyServerHeaders }, body: JSON.stringify({ userId: registerOptions.userId, username: passkeyName, cred: registerCred, rpID }), credentials }); const registerVerifyResult = await registerVerifyResponse.json(); if (!registerVerifyResult.verified) { throw new Error("Registration not verified"); } // Import the key pubKey = registerCred.response.publicKey; } if (!pubKey) { throw new Error("No public key returned from registration credential"); } if (!authenticatorId) { throw new Error("No authenticator id returned from registration credential"); } const authenticatorIdHash = keccak256(uint8ArrayToHexString(b64ToBytes(authenticatorId))); const spkiDer = Buffer.from(pubKey, "base64"); const key = await crypto.subtle.importKey("spki", spkiDer, { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]); // Export the key to the raw format const rawKey = await crypto.subtle.exportKey("raw", key); const rawKeyBuffer = Buffer.from(rawKey); // The first byte is 0x04 (uncompressed), followed by x and y coordinates (32 bytes each for P-256) const pubKeyX = rawKeyBuffer.subarray(1, 33).toString("hex"); const pubKeyY = rawKeyBuffer.subarray(33).toString("hex"); return { pubX: BigInt(`0x${pubKeyX}`), pubY: BigInt(`0x${pubKeyY}`), authenticatorId, authenticatorIdHash }; }; //# sourceMappingURL=toWebAuthnKey.js.map