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
Passkeys only work over HTTPS (except localhost). Never use HTTP in production.
Passkeys are bound to the domain. They cannot be used on other domains.
Use user verification (biometrics, PIN) for sensitive operations.
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.credentialsexists
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