Custom WebAuthnIdentity with Local Canister

Hi everyone,

I’m currently working with threshold-ECDSA for cross-chain key management and testing it on a locally deployed canister. I’m using a Vite + React frontend to interact with the canister, and for authentication, I’m leveraging WebAuthnIdentity from the Dfinity agent.

Everything works as expected when I use a local HttpAgent, and I’ve successfully set up the following:

    if (import.meta.env.VITE_DFX_NETWORK !== "ic") {
        console.log('using local development environment');
        // agent.host = '127.0.0.1:4943'
        
        agent.fetchRootKey().catch((err) => {
            console.error(err);
            console.warn(
                "Unable to fetch root key. Check to ensure that your local development environment is running"
            );
            console.error(err);
        });
    }

Below is my WebAuthnIdentity setup for creating authentication, I believe it should be possible to pass returned value from ICPWebauthn below to local HttpAgent



export async function handleWebauthnCreate() {
    const challenge = new Uint8Array(32);
    window.crypto.getRandomValues(challenge);

    const publicKeyCredentialCreationOptions: PublicKeyCredentialCreationOptions = {
        challenge: challenge,
        rp: {
            name: "My App",
          
        },
        user: {
            id: Uint8Array.from(window.crypto.getRandomValues(new Uint8Array(16))),
            name: "lee@webauthn.guide",
            displayName: "Lee"
        },
        pubKeyCredParams: [
            { alg: -7, type: "public-key" },    
            { alg: -257, type: "public-key" }  
        ],
        authenticatorSelection: {
            authenticatorAttachment: "platform", 
            residentKey: "required",            
            userVerification: "required"       
        },
        timeout: 60000,
        attestation: "none" 
    };

    const credential:any = await navigator.credentials.create({
        publicKey: publicKeyCredentialCreationOptions
    });

    console.log('Credential:', credential);
    let webauthNIdentity = await ICPWebauthn(credential.rawId, credential.response.attestationObject, credential.authenticatorAttachment)
    return webauthNIdentity;
}
  


export async function ICPWebauthn(rawId: ArrayBuffer, attestationObject: ArrayBuffer, authenticatorAttachment:any ){

    let icp = new ICPIdentity.WebAuthnIdentity(rawId, attestationObject, authenticatorAttachment)
    let principal = icp.getPrincipal()
    let publicKey = icp.getPublicKey()
    console.log('the principal ', principal.toString(), principal.toHex(), principal.toText())
    console.log('the public key ', publicKey.derKey, publicKey.toDer(), publicKey.rawKey, publicKey.toRaw)
    return icp

}

Now, I’m trying to test WebAuthnIdentity with the local canister. However, when I pass the identity returned by WebAuthnIdentity, I encounter an error.

Has anyone successfully used WebAuthnIdentity in a local development setup? Any advice or insights would be appreciated.

WebAuthn signatures from credentials.get are supported by the IC, signatures from credentials.create throws an error.

We ran into this with Internet Identity a while back, and recently when we revisited our WebAuthn implementation again.

Within Internet Identity, we work around this by creating a random ECDSA identity (used for registration flow) and allowing a user to use that identity principal after registration until they’ve used their WebAuthn identity principal once (credentials.get).

This work around avoids the need to do both a credentials.create and credentials.get in the registration flow, avoiding the need for a user to use their biometrics twice.

Also keep in mind you’ll need to store the public key of a passkey somewhere (likely in your canister) to be able to authenticate again later with the same passkey.

See the following identity implementation and its usage for a discoverable passkey flow internet-identity/src/frontend/src/lib/utils/discoverablePasskeyIdentity.ts at main · dfinity/internet-identity · GitHub

And this implementation and its usage for a non-discoverable passkey flow internet-identity/src/frontend/src/lib/utils/multiWebAuthnIdentity.ts at main · dfinity/internet-identity · GitHub