Passkey Operations

Complete guide to passkey registration, authentication, and transaction signing

Passkey Operations Guide

Passkeys provide a secure, passwordless authentication method using WebAuthn/FIDO2 standards. This guide covers the complete passkey lifecycle: registration, authentication, and transaction signing.

Overview

Passkeys are cryptographic credentials stored in secure hardware (like your device's secure enclave or a hardware security key). They provide:

  • Passwordless Authentication: No passwords to remember or manage
  • Hardware Security: Keys stored in secure hardware, never exposed
  • Phishing Resistant: Domain-bound credentials prevent phishing attacks
  • Cross-Platform: Works across devices with proper setup

Browser Compatibility

Passkeys require WebAuthn API support:

  • Chrome/Edge: Full support (version 67+)
  • Firefox: Full support (version 60+)
  • Safari: Full support (version 13+)
  • Opera: Full support (version 54+)

Passkeys require HTTPS (except for localhost). They will not work on HTTP connections.

Registration Flow

Step 1: Start Passkey Registration

Initiate the passkey registration process for a wallet.

import { startPasskeyRegistration } from 'cilantro-sdk/wallet';

// Start registration
const options = await startPasskeyRegistration('wallet-id');
console.log('Registration options:', options.data);

Step 2: Create Credential with WebAuthn API

Use the browser's WebAuthn API to create the credential.

// Use the options from step 1
const credential = await navigator.credentials.create({
  publicKey: options.data
});

console.log('Credential created:', credential.id);

Step 3: Verify and Complete Registration

Verify the credential and complete the signer creation.

import { verifyPasskeyRegistration } from 'cilantro-sdk/wallet';

// Verify and create signer
const signer = await verifyPasskeyRegistration('wallet-id', {
  response: credential.response,
  credentialId: credential.id
});

console.log('Passkey signer created:', signer.data.signerId);

Complete Registration Example

async function registerPasskey(walletId: string) {
  try {
    // Step 1: Get registration options
    const options = await startPasskeyRegistration(walletId);
    
    // Step 2: Create credential
    const credential = await navigator.credentials.create({
      publicKey: options.data
    }) as PublicKeyCredential;
    
    if (!credential) {
      throw new Error('Credential creation failed');
    }
    
    // Step 3: Verify and create signer
    const signer = await verifyPasskeyRegistration(walletId, {
      response: credential.response,
      credentialId: credential.id
    });
    
    console.log('✅ Passkey registered successfully');
    return signer.data;
    
  } catch (error) {
    if (error.name === 'NotSupportedError') {
      console.error('Passkeys not supported in this browser');
    } else if (error.name === 'NotAllowedError') {
      console.error('User cancelled or denied passkey creation');
    } else {
      console.error('Registration failed:', error);
    }
    throw error;
  }
}

Authentication Flow

Step 1: Start Passkey Authentication

Initiate the authentication process.

import { startPasskeyAuthentication } from 'cilantro-sdk/wallet';

// Start authentication
const options = await startPasskeyAuthentication('wallet-id', {
  credentialId: 'optional-credential-id' // Optional: specific credential
});

Step 2: Authenticate with WebAuthn API

Use the browser's WebAuthn API to authenticate.

// Use the options from step 1
const assertion = await navigator.credentials.get({
  publicKey: options.data
});

console.log('Authentication successful:', assertion.id);

Step 3: Verify Authentication

Verify the authentication response.

import { verifyPasskeyAuthentication } from 'cilantro-sdk/wallet';

// Verify authentication
const result = await verifyPasskeyAuthentication('wallet-id', {
  response: assertion.response,
  credentialId: assertion.id
});

console.log('Authentication verified:', result.data.verified);

Complete Authentication Example

async function authenticateWithPasskey(walletId: string, credentialId?: string) {
  try {
    // Step 1: Get authentication options
    const options = await startPasskeyAuthentication(walletId, {
      credentialId // Optional: specific credential
    });
    
    // Step 2: Authenticate
    const assertion = await navigator.credentials.get({
      publicKey: options.data
    }) as PublicKeyCredential;
    
    if (!assertion) {
      throw new Error('Authentication failed');
    }
    
    // Step 3: Verify
    const result = await verifyPasskeyAuthentication(walletId, {
      response: assertion.response,
      credentialId: assertion.id
    });
    
    console.log('✅ Authentication successful');
    return result.data;
    
  } catch (error) {
    if (error.name === 'NotAllowedError') {
      console.error('User cancelled authentication');
    } else {
      console.error('Authentication failed:', error);
    }
    throw error;
  }
}

Transaction Signing with Passkeys

Method 1: Using sendRawPasskeyTransaction

Send a transaction directly with passkey signature.

import { sendRawPasskeyTransaction } from 'cilantro-sdk/transactions';
import { Transaction } from '@solana/web3.js';

async function sendTransactionWithPasskey(
  walletId: string,
  recipientAddress: string,
  amountLamports: number
) {
  // Step 1: Build transaction
  const transaction = new Transaction();
  // ... add instructions to transaction
  
  // Step 2: Authenticate with passkey
  const authResult = await authenticateWithPasskey(walletId);
  
  // Step 3: Sign transaction with passkey
  const signedTx = await signTransactionWithPasskey(transaction, authResult);
  
  // Step 4: Send transaction
  const result = await sendRawPasskeyTransaction({
    walletId,
    transaction: signedTx.toString('base64'),
    passkeySignature: authResult.signature
  });
  
  return result.data;
}

Method 2: Using prepareTransaction + Passkey Signing

Prepare transaction and sign with passkey.

import { prepareTransaction, submitTransaction } from 'cilantro-sdk/wallet';

async function sendSOLWithPasskey(
  walletId: string,
  recipientAddress: string,
  amountLamports: number
) {
  // Step 1: Get signer public key
  const signers = await listSigners(walletId);
  const passkeySigner = signers.data.find(s => s.type === 'PASSKEY');
  
  if (!passkeySigner) {
    throw new Error('No passkey signer found');
  }
  
  // Step 2: Prepare transaction
  const prepared = await prepareTransaction(walletId, {
    type: 'SEND_SOL',
    signerPubkey: passkeySigner.publicKey,
    sendSolParams: {
      recipientAddress,
      amountLamports
    }
  });
  
  // Step 3: Authenticate with passkey
  const authResult = await authenticateWithPasskey(walletId, passkeySigner.credentialId);
  
  // Step 4: Sign transaction
  const transaction = Transaction.from(
    Buffer.from(prepared.data.serializedTransaction, 'base64')
  );
  
  // Sign with passkey (implementation depends on your signing method)
  const signedTx = await signWithPasskey(transaction, authResult);
  
  // Step 5: Submit
  const result = await submitTransaction(walletId, {
    signedTransaction: signedTx.toString('base64')
  });
  
  return result.data;
}

Best Practices

Error Handling

async function handlePasskeyError(error: Error) {
  switch (error.name) {
    case 'NotSupportedError':
      // Browser doesn't support WebAuthn
      return 'Your browser does not support passkeys. Please use a modern browser.';
    
    case 'NotAllowedError':
      // User cancelled or denied
      return 'Passkey operation was cancelled.';
    
    case 'InvalidStateError':
      // Credential already exists or doesn't exist
      return 'Passkey credential issue. Please try again.';
    
    case 'SecurityError':
      // Security violation (e.g., not HTTPS)
      return 'Security error. Please ensure you are using HTTPS.';
    
    default:
      return 'An error occurred with passkey operation.';
  }
}

User Experience

// Show user-friendly messages
async function registerPasskeyWithUX(walletId: string) {
  try {
    // Show loading state
    showLoading('Setting up passkey...');
    
    const options = await startPasskeyRegistration(walletId);
    
    // Update message
    showLoading('Please use your device to authenticate...');
    
    const credential = await navigator.credentials.create({
      publicKey: options.data
    });
    
    showLoading('Completing registration...');
    
    const signer = await verifyPasskeyRegistration(walletId, {
      response: credential.response,
      credentialId: credential.id
    });
    
    showSuccess('Passkey registered successfully!');
    return signer.data;
    
  } catch (error) {
    const message = await handlePasskeyError(error);
    showError(message);
    throw error;
  } finally {
    hideLoading();
  }
}

Multiple Passkeys

// Register multiple passkeys for backup
async function registerMultiplePasskeys(walletId: string) {
  const passkeys = [];
  
  // Register primary passkey
  const primary = await registerPasskey(walletId);
  passkeys.push(primary);
  
  // Register backup passkey
  const backup = await registerPasskey(walletId);
  passkeys.push(backup);
  
  console.log(`Registered ${passkeys.length} passkeys`);
  return passkeys;
}

Security Considerations

HTTPS Required

Passkeys only work over HTTPS (except localhost). Never use HTTP in production.

Domain Binding

Passkeys are bound to the domain. They cannot be used on other domains.

User Verification

Use user verification (biometrics, PIN) for sensitive operations.

Backup Methods

Always provide backup authentication methods (email, phone) in case passkey is lost.

Troubleshooting

Common Issues

Issue: "NotSupportedError"

  • Solution: Ensure browser supports WebAuthn (Chrome 67+, Firefox 60+, Safari 13+)
  • Check: navigator.credentials exists

Issue: "NotAllowedError"

  • Solution: User may have cancelled. Provide clear instructions
  • Check: User interaction is required

Issue: "InvalidStateError"

  • Solution: Credential may already exist or be invalid
  • Check: Try registering a new credential

Issue: "SecurityError"

  • Solution: Must use HTTPS (except localhost)
  • Check: Verify SSL certificate is valid

Next Steps

Passkey Operations | Cilantro Smart Wallet Docs | Cilantro Smart Wallet