How to Authenticate Users with Passkeys
Create and authenticate passkeys, handle errors, and configure auto-lock for the vault.
How to Authenticate Users with Passkeys
This guide shows you how to register a new passkey, authenticate a returning user, and manage vault lock state.
Create a passkey
Call createPasskey with the user's handle (their chosen alias or identifier). This triggers the browser's WebAuthn credential creation prompt.
import { createPasskey } from '@hyperauth/sdk';
const { credential, credentialId } = await createPasskey('alice');The returned credential is a base64-encoded JSON string ready to pass to client.generate. The credentialId is the raw WebAuthn credential ID — store it if you need to link passkeys across sessions.
If you need to prevent duplicate passkeys on the same device, pass the existing credential IDs in excludeCredentials:
const { credential, credentialId } = await createPasskey('alice', undefined, {
excludeCredentials: [
{ id: existingRawId, type: 'public-key' },
],
});If the passkey creation fails due to a cancelled prompt or an unsupported algorithm, createPasskey throws a WebAuthnError. The error message indicates whether the user cancelled or the authenticator returned an incompatible algorithm (HyperAuth requires ES256 / P-256).
import { WebAuthnError } from '@hyperauth/sdk';
try {
const result = await createPasskey('alice');
} catch (err) {
if (err instanceof WebAuthnError) {
// User cancelled, or authenticator used an unsupported algorithm
console.error(err.message);
}
}Generate an identity from the passkey
After creating a passkey, pass the serialized credential to the vault to derive the identity and write it to the encrypted database:
const result = await client.generate(credential, {
identifier: 'alice',
channel: 'handle',
});Authenticate a returning user
authenticatePasskey prompts the user to select and verify an existing passkey. It returns the credential ID of the selected passkey.
import { authenticatePasskey } from '@hyperauth/sdk';
const credentialId = await authenticatePasskey();If you serve from a custom domain, pass the relying party ID explicitly:
const credentialId = await authenticatePasskey('auth.example.com');Use the returned credential ID to locate the user's encrypted vault database, then load it:
const database = await fetchVaultDatabase(credentialId); // your storage layer
await client.load(database);Check lock status before operations
If you need to check whether the vault is locked before performing an operation:
const locked = await client.isLocked();
if (locked) {
// Prompt the user to re-authenticate
}isLocked calls client.status() internally. If you also need additional status fields, call status directly:
const { locked, did, boot } = await client.status();Unlock after lock
If the vault has been locked (by auto-lock or an explicit call), unlock it by supplying the database bytes and the encryption key derived from the passkey:
const result = await client.unlock(databaseBytes, encryptionKey);
if (!result.success) {
console.error('Unlock failed:', result.error);
}Configure auto-lock
By default the vault locks after 5 minutes of inactivity. Set a custom timeout at creation time:
const client = await createClient({
autoLockTimeout: 10 * 60 * 1000, // 10 minutes
});Set it to 0 to disable auto-lock entirely:
const client = await createClient({ autoLockTimeout: 0 });To change the timeout after client creation:
client.setAutoLockTimeout(2 * 60 * 1000); // 2 minutesRegister a callback to be notified when auto-lock fires. The callback receives the encrypted database bytes so you can persist them:
client.setAutoLockCallback((database) => {
localStorage.setItem('vault', JSON.stringify(database));
});Lock explicitly
Call lock directly when the user signs out or navigates away:
const result = await client.lock();
if (result.success && result.database) {
// Persist the locked database
localStorage.setItem('vault', JSON.stringify(Array.from(result.database)));
}