Complete the passwordless prerequisites and choose passkeys only after defining a tested fallback and account-recovery path.
Passkeys (WebAuthn) Authentication
Passkeys use origin-bound WebAuthn credentials with platform or hardware authenticators, which are designed to reduce phishing risk. This Beta implementation has not yet published conformance or abuse-case results.
Browser Compatibility Check
Before implementing passkeys, check if the user’s browser supports WebAuthn:
import { SsoClient } from '@drmhse/sso-sdk';
const sso = new SsoClient({
baseURL: 'https://sso.example.com'
});
// Check if WebAuthn is supported
if (!sso.passkeys.isSupported()) {
console.log('Passkeys not supported - show password login instead');
return;
}
// Check if platform authenticator is available (Touch ID, Face ID, etc.)
const hasPlatformAuth = await sso.passkeys.isPlatformAuthenticatorAvailable();
if (hasPlatformAuth) {
console.log('Biometric authentication available');
}
Registering a Passkey
Users must register a passkey while authenticated. This is typically done from account settings after the user has logged in with another method.
async function registerPasskey() {
// User must be authenticated first
const accessToken = localStorage.getItem('sso_access_token');
if (!accessToken) {
throw new Error('User must be logged in to register a passkey');
}
sso.setAuthToken(accessToken);
try {
// Register the passkey with a friendly name
const passkeyId = await sso.passkeys.register('My MacBook Pro');
console.log('Passkey registered successfully:', passkeyId);
// Show success message to user
return passkeyId;
} catch (error) {
console.error('Passkey registration failed:', error);
// Handle errors: user cancelled, authenticator not available, etc.
throw error;
}
}
Logging In with a Passkey
Once registered, users can authenticate using their passkey:
async function loginWithPasskey(email: string) {
try {
// Initiate passkey authentication
const result = await sso.passkeys.login(email);
// Store the token
localStorage.setItem('sso_access_token', result.token);
sso.setAuthToken(result.token);
console.log('Logged in as:', result.user_id);
// Redirect to dashboard
window.location.href = '/dashboard';
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('no passkeys registered')) {
// User has no passkeys - offer alternative login
console.error('No passkeys registered for this account');
} else {
console.error('Passkey login failed:', error.message);
}
}
throw error;
}
}
Complete React Passkey Implementation
import { useState } from 'react';
import { SsoClient, SsoApiError } from '@drmhse/sso-sdk';
const sso = new SsoClient({
baseURL: process.env.REACT_APP_SSO_URL!
});
export function PasskeyLogin() {
const [email, setEmail] = useState('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [isSupported, setIsSupported] = useState(true);
// Check browser support on mount
useEffect(() => {
setIsSupported(sso.passkeys.isSupported());
}, []);
const handlePasskeyLogin = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setIsLoading(true);
try {
const result = await sso.passkeys.login(email);
// Store token and redirect
localStorage.setItem('sso_access_token', result.token);
sso.setAuthToken(result.token);
window.location.href = '/dashboard';
} catch (err) {
setIsLoading(false);
if (err instanceof SsoApiError) {
if (err.statusCode === 404) {
setError('No passkeys registered for this account. Please use another login method.');
} else {
setError(err.message);
}
} else if (err instanceof Error) {
if (err.name === 'NotAllowedError') {
setError('Authentication cancelled or timed out');
} else {
setError('Passkey authentication failed');
}
}
}
};
if (!isSupported) {
return (
<div className="alert alert-warning">
Your browser does not support passkeys. Please use a modern browser or try password login.
</div>
);
}
return (
<div className="passkey-login">
<h2>Sign in with Passkey</h2>
<p>Use Touch ID, Face ID, or your security key to sign in securely</p>
<form onSubmit={handlePasskeyLogin}>
<div className="form-group">
<label>Email Address</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
required
/>
</div>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Authenticating...' : 'Sign in with Passkey'}
</button>
</form>
{error && <div className="alert alert-error">{error}</div>}
<div className="alternative-methods">
<a href="/login/password">Sign in with password instead</a>
</div>
</div>
);
}
export function PasskeyRegistration() {
const [deviceName, setDeviceName] = useState('');
const [error, setError] = useState('');
const [success, setSuccess] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const handleRegister = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setSuccess(false);
setIsLoading(true);
try {
// User must already be authenticated
const passkeyId = await sso.passkeys.register(deviceName || undefined);
setSuccess(true);
setDeviceName('');
console.log('Registered passkey:', passkeyId);
} catch (err) {
setIsLoading(false);
if (err instanceof Error) {
if (err.name === 'NotAllowedError') {
setError('Registration cancelled or not allowed');
} else if (err.name === 'InvalidStateError') {
setError('A passkey is already registered on this device');
} else {
setError(err.message);
}
}
}
};
if (!sso.passkeys.isSupported()) {
return (
<div className="alert alert-warning">
Your browser does not support passkeys
</div>
);
}
return (
<div className="passkey-registration">
<h3>Register a Passkey</h3>
<p>Add a passkey to sign in faster and more securely</p>
{success ? (
<div className="alert alert-success">
Passkey registered successfully! You can now use it to sign in.
</div>
) : (
<form onSubmit={handleRegister}>
<div className="form-group">
<label>Device Name (Optional)</label>
<input
type="text"
value={deviceName}
onChange={(e) => setDeviceName(e.target.value)}
placeholder="e.g., My MacBook Pro"
/>
<small>Give this passkey a name to identify which device it's on</small>
</div>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Registering...' : 'Register Passkey'}
</button>
</form>
)}
{error && <div className="alert alert-error">{error}</div>}
</div>
);
}
Best Practices
Passkey Security
- Always use HTTPS: WebAuthn requires a secure context
- Prevent account lockout: Always offer alternative authentication methods
- User-friendly device names: Encourage users to name their passkeys
- Handle errors gracefully: Users may cancel authentication or not have a compatible device
- Support multiple passkeys: Allow users to register passkeys on multiple devices
Troubleshooting
Passkey Issues
“WebAuthn not supported”
- Ensure the site is accessed via HTTPS (localhost is exempt)
- Check browser compatibility (Chrome 67+, Safari 14+, Firefox 60+)
- Verify user has a compatible authenticator
“Registration failed”
- User may have cancelled the operation
- Authenticator might already have a credential for this site
- Check for conflicting browser extensions
“Authentication failed”
- User may have no registered passkeys
- Passkey might have been removed from the authenticator
- Network or server errors
Next steps
- Return to the passwordless mechanism chooser.
- Compare the Magic Links guide.
- Look up exact methods in the Passkeys SDK reference.