useRecovery
Account recovery operations for when users lose access to their passkeys.
Recovery Flow: Start recovery with an email address, then complete it with passkeys or backup codes from the active recovery session.
Import
import { useRecovery } from '@heliorim/sdk-react';Type Signature
interface RecoveryState {
isInitiating: boolean;
isRecovering: boolean;
recoveryMethod: RecoveryMethod | null;
availableMethods: RecoveryMethod[];
backupCodes: string[];
recoverySession: RecoverySession | null;
error: Error | null;
}
interface RecoveryActions {
initiateRecovery: (email: string) => Promise<void>;
selectMethod: (method: RecoveryMethod) => Promise<void>;
completePasskeyRecovery: () => Promise<Session>;
generateBackupCodes: () => Promise<string[]>;
verifyBackupCode: (code: string) => Promise<Session>;
addRecoveryPasskey: () => Promise<void>;
cancelRecovery: () => Promise<void>;
clearError: () => void;
}
type RecoveryMethod = 'passkey' | 'guardian' | 'backup-codes';
interface RecoverySession {
id: string;
expiresAt: number;
attemptsRemaining: number;
}
type UseRecoveryReturn = RecoveryState & RecoveryActions;Basic Usage
function AccountRecovery() {
const {
isInitiating,
isRecovering,
availableMethods,
recoverySession,
initiateRecovery,
selectMethod,
completePasskeyRecovery,
verifyBackupCode
} = useRecovery();
const [email, setEmail] = useState('');
const startRecovery = async () => {
await initiateRecovery(email);
await selectMethod('passkey');
await completePasskeyRecovery();
};
const useBackupCode = async (code: string) => {
await selectMethod('backup-codes');
await verifyBackupCode(code);
};
return (
<div>
<h2>Lost Your Passkey?</h2>
<div>
<h3>Start recovery</h3>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
/>
<button onClick={startRecovery} disabled={isInitiating || isRecovering}>
Recover account
</button>
</div>
{recoverySession ? (
<p>
Available methods: {availableMethods.join(', ')}. Attempts remaining:
{' '}{recoverySession.attemptsRemaining}
</p>
) : null}
</div>
);
}Recovery Methods
Passkey Recovery
const {
initiateRecovery,
selectMethod,
completePasskeyRecovery
} = useRecovery();
// Step 1: Identify the account by email
await initiateRecovery('user@example.com');
// Step 2: Choose passkey recovery
await selectMethod('passkey');
// Step 3: Run the WebAuthn assertion flow
await completePasskeyRecovery();Backup Codes
const {
initiateRecovery,
selectMethod,
generateBackupCodes,
verifyBackupCode,
backupCodes
} = useRecovery();
// Generate codes while authenticated
const codes = await generateBackupCodes();
// Returns single-use codes like: "XXXX-XXXX-XXXX"
// Save component to display codes
function BackupCodeDisplay({ codes }) {
const downloadCodes = () => {
const blob = new Blob([codes.join('\n')], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'heliorim-backup-codes.txt';
a.click();
};
return (
<div className="backup-codes">
<h3>⚠️ Save These Codes</h3>
<p>Each code can only be used once</p>
<ul>
{codes.map(code => <li key={code}>{code}</li>)}
</ul>
<button onClick={downloadCodes}>Download Codes</button>
</div>
);
}
// Use a backup code for an active recovery session
await initiateRecovery('user@example.com');
await selectMethod('backup-codes');
await verifyBackupCode('ABCD-EFGH-IJKL');Recovery Passkey
const { addRecoveryPasskey } = useRecovery();
// Recovery passkey registration is reserved for a future backend release.
// The current hook throws until that support ships.
function AddRecoveryDevice() {
const handleAdd = async () => {
try {
await addRecoveryPasskey();
} catch (err) {
console.error('Recovery passkey registration not yet implemented in backend');
}
};
return (
<button onClick={handleAdd}>
Add Recovery Device (coming soon)
</button>
);
}Complete Recovery Flow
function RecoveryWizard() {
const {
availableMethods,
recoverySession,
initiateRecovery,
selectMethod,
completePasskeyRecovery,
verifyBackupCode,
cancelRecovery,
isInitiating,
isRecovering,
error,
recoveryMethod
} = useRecovery();
const [email, setEmail] = useState('');
const [backupCode, setBackupCode] = useState('');
const startRecovery = async () => {
try {
await initiateRecovery(email);
} catch (err) {
console.error('Recovery initiation failed:', err);
}
};
const recoverWithPasskey = async () => {
try {
await selectMethod('passkey');
await completePasskeyRecovery();
} catch (err) {
console.error('Passkey recovery failed:', err);
}
};
const recoverWithBackupCode = async () => {
try {
await selectMethod('backup-codes');
await verifyBackupCode(backupCode);
} catch (err) {
console.error('Backup-code recovery failed:', err);
}
};
return (
<div>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Your email"
/>
<button onClick={startRecovery} disabled={isInitiating || isRecovering}>
Start recovery
</button>
{recoverySession ? (
<div>
<p>Available methods: {availableMethods.join(', ')}</p>
<p>Attempts remaining: {recoverySession.attemptsRemaining}</p>
<button onClick={recoverWithPasskey} disabled={isRecovering}>
Recover with passkey
</button>
<input
type="text"
value={backupCode}
onChange={(e) => setBackupCode(e.target.value)}
placeholder="Backup code"
/>
<button onClick={recoverWithBackupCode} disabled={isRecovering}>
Recover with backup code
</button>
<button onClick={cancelRecovery}>Cancel</button>
</div>
) : null}
<p>Selected method: {recoveryMethod ?? 'none'}</p>
{error ? <p>{error.message}</p> : null}
</div>
);
}Security Best Practices
Recommended Setup
- Register passkeys on multiple devices
- Generate and securely store backup codes
- Keep recovery email up-to-date
- Test recovery process periodically
Important Notes
- Backup codes are single-use only
- Recovery sessions expire after 15 minutes
- Recovery requires a verified account email
- Backup codes are only available after authenticated setup