usePasskey

Manage passkey registration, authentication, and credential operations.

Framework Support: Available in all HelioRim SDK frameworks

Import

import { usePasskey } from '@heliorim/sdk-react';
// or
import { usePasskey } from '@heliorim/sdk-vue';

Type Signature

interface PasskeyState {
  isRegistering: boolean;
  isAuthenticating: boolean;
  credentials: Credential[];
  currentCredential: Credential | null;
  error: Error | null;
  isSupported: boolean;
  isConditionalUIAvailable: boolean;
}

interface PasskeyActions {
  register: (options: RegisterOptions) => Promise<Credential>;
  authenticate: (options?: AuthenticateOptions) => Promise<void>;
  listCredentials: () => Promise<Credential[]>;
  deleteCredential: (credentialId: string) => Promise<void>;
  renameCredential: (credentialId: string, name: string) => Promise<void>;
  clearError: () => void;
}

interface RegisterOptions {
  email: string;
  displayName: string;
  userVerification?: 'required' | 'preferred' | 'discouraged';
  authenticatorAttachment?: 'platform' | 'cross-platform';
  residentKey?: 'required' | 'preferred' | 'discouraged';
}

interface AuthenticateOptions {
  email?: string;
  userVerification?: 'required' | 'preferred' | 'discouraged';
  conditionalUI?: boolean;
}

interface Credential {
  id: string;
  publicKey: string;
  name: string;
  createdAt: number;
  lastUsedAt?: number;
  authenticatorAttachment?: 'platform' | 'cross-platform';
  backupEligible?: boolean;
  backupState?: boolean;
}

type UsePasskeyReturn = PasskeyState & PasskeyActions;

Basic Usage

function PasskeyManager() {
  const {
    isRegistering,
    isAuthenticating,
    credentials,
    isSupported,
    register,
    authenticate,
    listCredentials,
    deleteCredential
  } = usePasskey();

  const handleRegister = async () => {
    try {
      const credential = await register({
        email: 'user@example.com',
        displayName: 'John Doe',
        userVerification: 'required',
        authenticatorAttachment: 'platform'
      });
      console.log('Registered:', credential);
    } catch (err) {
      console.error('Registration failed:', err);
    }
  };

  const handleLogin = async () => {
    try {
      await authenticate({
        userVerification: 'required'
      });
      // Authentication successful
    } catch (err) {
      console.error('Authentication failed:', err);
    }
  };

  if (!isSupported) {
    return <div>Passkeys not supported on this device</div>;
  }

  return (
    <div>
      <button
        onClick={handleRegister}
        disabled={isRegistering}
      >
        {isRegistering ? 'Registering...' : 'Register Passkey'}
      </button>

      <button
        onClick={handleLogin}
        disabled={isAuthenticating}
      >
        {isAuthenticating ? 'Authenticating...' : 'Sign In'}
      </button>

      <div>
        <h3>Your Passkeys</h3>
        {credentials.map(cred => (
          <div key={cred.id}>
            <span>{cred.name}</span>
            <button onClick={() => deleteCredential(cred.id)}>
              Delete
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}

Return Values

isRegistering

boolean - Registration ceremony in progress

isAuthenticating

boolean - Authentication ceremony in progress

credentials

Credential[] - List of user's registered passkeys

isSupported

boolean - Whether WebAuthn is supported in current browser

isConditionalUIAvailable

boolean - Whether conditional UI (autofill) is available

register(options)

Promise<Credential> - Register a new passkey for the user

authenticate(options?)

Promise<void> - Authenticate using a passkey

listCredentials()

Promise<Credential[]> - Fetch all user's passkeys

deleteCredential(id)

Promise<void> - Delete a specific passkey

renameCredential(id, name)

Promise<void> - Rename a passkey for easier identification

Advanced Features

Conditional UI (Autofill)

function LoginWithAutofill() {
  const { authenticate, isConditionalUIAvailable } = usePasskey();

  useEffect(() => {
    if (isConditionalUIAvailable) {
      // Start conditional UI - passkeys appear in autofill
      authenticate({ conditionalUI: true }).catch(() => {
        // User didn't select a passkey from autofill
      });
    }
  }, [isConditionalUIAvailable]);

  return (
    <form>
      <input
        type="email"
        autoComplete="username webauthn"
        placeholder="Email"
      />
      <button type="submit">Continue</button>
    </form>
  );
}

Platform vs Cross-Platform

function RegisterOptions() {
  const { register } = usePasskey();

  const registerPlatform = () => {
    // Prefers built-in authenticator (Touch ID, Windows Hello)
    register({
      email: 'user@example.com',
      displayName: 'User',
      authenticatorAttachment: 'platform',
      residentKey: 'required' // Discoverable credential
    });
  };

  const registerCrossPlatform = () => {
    // Allows USB security keys, phones
    register({
      email: 'user@example.com',
      displayName: 'User',
      authenticatorAttachment: 'cross-platform'
    });
  };

  return (
    <>
      <button onClick={registerPlatform}>
        Use This Device
      </button>
      <button onClick={registerCrossPlatform}>
        Use Security Key
      </button>
    </>
  );
}

Backup State Detection

function CredentialList() {
  const { credentials } = usePasskey();

  return (
    <ul>
      {credentials.map(cred => (
        <li key={cred.id}>
          <span>{cred.name}</span>
          {cred.backupEligible && (
            <span className="badge">
              {cred.backupState ? '☁️ Synced' : '📱 Device-only'}
            </span>
          )}
        </li>
      ))}
    </ul>
  );
}

User Verification Levels

const { authenticate } = usePasskey();

// High security - always require biometric/PIN
await authenticate({
  userVerification: 'required'
});

// Balanced - use if available
await authenticate({
  userVerification: 'preferred'
});

// Low friction - skip if possible
await authenticate({
  userVerification: 'discouraged'
});

Error Handling

const { register, error } = usePasskey();

try {
  await register(options);
} catch (err) {
  switch(err.name) {
    case 'NotAllowedError':
      // User cancelled or timeout
      break;
    case 'InvalidStateError':
      // Credential already exists
      break;
    case 'NotSupportedError':
      // Algorithm or option not supported
      break;
    case 'SecurityError':
      // Origin not allowed
      break;
    case 'AbortError':
      // Operation was aborted
      break;
  }
}

Browser Compatibility

WebAuthn Support Required:

  • Chrome/Edge 67+ (Desktop & Mobile)
  • Safari 14+ (macOS 11+, iOS 14+)
  • Firefox 60+ (Limited mobile support)
  • No support: Internet Explorer, older browsers

Best Practices

✓ DO

  • Check isSupported before showing passkey options
  • Provide fallback authentication methods
  • Allow users to name their credentials
  • Show backup state to users
  • Handle all error cases gracefully

✗ DON'T

  • Force passkey-only authentication
  • Store private keys (they never leave the authenticator)
  • Assume all devices support platform authenticators
  • Skip user verification for sensitive operations

Related Hooks