🌐 Web SDK
The passkeyme-web-sdk
provides low-level passkey ceremony implementation for web browsers. This SDK handles only WebAuthn/FIDO2 operations and requires backend integration for complete authentication flows.
SDK Purpose
This is a low-level SDK for custom implementations. Most web developers should use:
- React SDK for React applications with inline components
- JavaScript SDK for other frameworks with hosted authentication
📦 Installation
npm install passkeyme-web-sdk
🚀 Quick Start
Basic Setup
import { PasskeymeWebSDK } from 'passkeyme-web-sdk';
const sdk = new PasskeymeWebSDK({
debug: process.env.NODE_ENV === 'development'
});
Registration Flow
// 1. Get challenge from your backend (which calls PasskeyMe API)
const registrationChallenge = await fetch('/api/start-registration', {
method: 'POST',
body: JSON.stringify({ username: 'user@example.com' })
}).then(r => r.json());
// 2. Perform passkey registration ceremony
try {
const result = await sdk.register({
username: 'user@example.com',
displayName: 'User Name',
challenge: registrationChallenge.challenge,
rp: registrationChallenge.rp,
user: registrationChallenge.user,
pubKeyCredParams: registrationChallenge.pubKeyCredParams,
timeout: registrationChallenge.timeout,
attestation: registrationChallenge.attestation
});
if (result.success) {
// 3. Send credential to backend to complete registration
const completion = await fetch('/api/complete-registration', {
method: 'POST',
body: JSON.stringify({
credential: result.credential,
username: 'user@example.com'
})
});
console.log('Registration successful!');
}
} catch (error) {
console.error('Registration failed:', error);
}
Authentication Flow
// 1. Get challenge from your backend
const authChallenge = await fetch('/api/start-authentication', {
method: 'POST',
body: JSON.stringify({ username: 'user@example.com' })
}).then(r => r.json());
// 2. Perform passkey authentication ceremony
try {
const result = await sdk.authenticate({
username: 'user@example.com', // Optional for discoverable credentials
challenge: authChallenge.challenge,
rpId: authChallenge.rpId,
allowCredentials: authChallenge.allowCredentials,
timeout: authChallenge.timeout,
userVerification: authChallenge.userVerification
});
if (result.success) {
// 3. Send assertion to backend to complete authentication
const completion = await fetch('/api/complete-authentication', {
method: 'POST',
body: JSON.stringify({
assertion: result.assertion,
username: 'user@example.com'
})
});
const user = await completion.json();
console.log('Authentication successful!', user);
}
} catch (error) {
console.error('Authentication failed:', error);
}
🔧 API Reference
PasskeymeWebSDK Constructor
interface WebSDKConfig {
/** Enable debug logging */
debug?: boolean;
/** Custom timeout for operations (ms) */
timeout?: number;
/** User verification requirement */
userVerification?: 'required' | 'preferred' | 'discouraged';
}
const sdk = new PasskeymeWebSDK(config);
Registration Method
interface RegistrationOptions {
username: string;
displayName: string;
challenge: string;
rp: {
name: string;
id: string;
};
user: {
id: string;
name: string;
displayName: string;
};
pubKeyCredParams: Array<{
type: 'public-key';
alg: number;
}>;
timeout?: number;
attestation?: 'none' | 'indirect' | 'direct';
authenticatorSelection?: {
authenticatorAttachment?: 'platform' | 'cross-platform';
requireResidentKey?: boolean;
userVerification?: 'required' | 'preferred' | 'discouraged';
};
excludeCredentials?: Array<{
type: 'public-key';
id: string;
}>;
}
interface RegistrationResult {
success: boolean;
credential?: {
id: string;
rawId: ArrayBuffer;
response: {
attestationObject: ArrayBuffer;
clientDataJSON: ArrayBuffer;
};
type: 'public-key';
};
error?: string;
}
const result = await sdk.register(options);
Authentication Method
interface AuthenticationOptions {
username?: string; // Optional for discoverable credentials
challenge: string;
rpId?: string;
allowCredentials?: Array<{
type: 'public-key';
id: string;
}>;
timeout?: number;
userVerification?: 'required' | 'preferred' | 'discouraged';
}
interface AuthenticationResult {
success: boolean;
assertion?: {
credential: {
id: string;
rawId: ArrayBuffer;
response: {
authenticatorData: ArrayBuffer;
clientDataJSON: ArrayBuffer;
signature: ArrayBuffer;
userHandle?: ArrayBuffer;
};
type: 'public-key';
};
};
error?: string;
}
const result = await sdk.authenticate(options);
Utility Methods
// Check if WebAuthn is supported
const isSupported = sdk.isSupported();
// Check if platform authenticator is available
const isPlatformAuthenticatorAvailable = await sdk.isPlatformAuthenticatorAvailable();
// Get supported algorithms
const algorithms = sdk.getSupportedAlgorithms();
🔗 Backend Integration
Required Backend Endpoints
Your backend needs these endpoints to integrate with PasskeyMe API:
// POST /api/start-registration
app.post('/api/start-registration', async (req, res) => {
const { username } = req.body;
const response = await fetch(`${PASSKEYME_API}/webauthn/${APP_ID}/start_registration`, {
method: 'POST',
headers: {
'x-api-key': PASSKEYME_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ username })
});
const challenge = await response.json();
res.json(challenge);
});
// POST /api/complete-registration
app.post('/api/complete-registration', async (req, res) => {
const { credential, username } = req.body;
const response = await fetch(`${PASSKEYME_API}/webauthn/${APP_ID}/complete_registration`, {
method: 'POST',
headers: {
'x-api-key': PASSKEYME_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ credential, username })
});
const result = await response.json();
res.json(result);
});
// POST /api/start-authentication
app.post('/api/start-authentication', async (req, res) => {
const { username } = req.body;
const response = await fetch(`${PASSKEYME_API}/webauthn/${APP_ID}/start_authentication`, {
method: 'POST',
headers: {
'x-api-key': PASSKEYME_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ username })
});
const challenge = await response.json();
res.json(challenge);
});
// POST /api/complete-authentication
app.post('/api/complete-authentication', async (req, res) => {
const { assertion, username } = req.body;
const response = await fetch(`${PASSKEYME_API}/webauthn/${APP_ID}/complete_authentication`, {
method: 'POST',
headers: {
'x-api-key': PASSKEYME_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ assertion, username })
});
const result = await response.json();
res.json(result);
});
🎨 Custom UI Examples
Registration Component
import { PasskeymeWebSDK } from 'passkeyme-web-sdk';
class PasskeyRegistration {
private sdk = new PasskeymeWebSDK({ debug: true });
async registerPasskey(username: string, displayName: string) {
try {
// Show loading state
this.showLoading('Creating your passkey...');
// Get challenge from backend
const challenge = await this.getRegistrationChallenge(username);
// Update UI
this.showLoading('Touch your authenticator...');
// Perform registration
const result = await this.sdk.register({
username,
displayName,
...challenge
});
if (result.success) {
// Complete registration
await this.completeRegistration(result.credential, username);
this.showSuccess('Passkey created successfully!');
} else {
throw new Error(result.error || 'Registration failed');
}
} catch (error) {
this.showError(`Registration failed: ${error.message}`);
}
}
private async getRegistrationChallenge(username: string) {
const response = await fetch('/api/start-registration', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username })
});
if (!response.ok) {
throw new Error('Failed to get registration challenge');
}
return response.json();
}
private async completeRegistration(credential: any, username: string) {
const response = await fetch('/api/complete-registration', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential, username })
});
if (!response.ok) {
throw new Error('Failed to complete registration');
}
return response.json();
}
private showLoading(message: string) {
// Update your UI loading state
console.log(message);
}
private showSuccess(message: string) {
// Update your UI success state
console.log(message);
}
private showError(message: string) {
// Update your UI error state
console.error(message);
}
}
Authentication Component
class PasskeyAuthentication {
private sdk = new PasskeymeWebSDK({ debug: true });
async authenticateWithPasskey(username?: string) {
try {
// Show loading state
this.showLoading('Preparing authentication...');
// Get challenge from backend
const challenge = await this.getAuthenticationChallenge(username);
// Update UI for biometric prompt
this.showLoading('Touch your authenticator...');
// Perform authentication
const result = await this.sdk.authenticate({
username,
...challenge
});
if (result.success) {
// Complete authentication
const user = await this.completeAuthentication(result.assertion, username);
this.showSuccess('Welcome back!');
return user;
} else {
throw new Error(result.error || 'Authentication failed');
}
} catch (error) {
this.showError(`Authentication failed: ${error.message}`);
throw error;
}
}
private async getAuthenticationChallenge(username?: string) {
const response = await fetch('/api/start-authentication', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username })
});
if (!response.ok) {
throw new Error('Failed to get authentication challenge');
}
return response.json();
}
private async completeAuthentication(assertion: any, username?: string) {
const response = await fetch('/api/complete-authentication', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ assertion, username })
});
if (!response.ok) {
throw new Error('Failed to complete authentication');
}
return response.json();
}
private showLoading(message: string) {
// Update your UI loading state
console.log(message);
}
private showSuccess(message: string) {
// Update your UI success state
console.log(message);
}
private showError(message: string) {
// Update your UI error state
console.error(message);
}
}
🛡️ Security Best Practices
API Key Protection
// ✅ Secure - API keys only on backend
const challenge = await fetch('/api/start-auth'); // Backend has API key
// ❌ Never expose API keys in client code
const sdk = new PasskeymeWebSDK({ apiKey: 'your-key' }); // Don't do this!
Challenge Validation
// ✅ Validate challenges come from your backend
const validateChallenge = (challenge: any) => {
if (!challenge.challenge || !challenge.rp) {
throw new Error('Invalid challenge format');
}
if (challenge.rp.id !== window.location.hostname) {
throw new Error('Challenge RP ID mismatch');
}
return challenge;
};
const challenge = validateChallenge(await getChallenge());
Error Handling
try {
const result = await sdk.authenticate(options);
} catch (error) {
if (error.name === 'NotSupportedError') {
// Show fallback authentication method
showPasswordLogin();
} else if (error.name === 'NotAllowedError') {
// User cancelled or timeout
showMessage('Authentication cancelled. Please try again.');
} else {
// Other errors
showMessage('Authentication failed. Please try again.');
}
}
📚 Framework Integration
React Integration
import { useCallback, useState } from 'react';
import { PasskeymeWebSDK } from 'passkeyme-web-sdk';
const usePasskeyAuth = () => {
const [sdk] = useState(() => new PasskeymeWebSDK({ debug: true }));
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const authenticate = useCallback(async (username?: string) => {
setLoading(true);
setError(null);
try {
const challenge = await fetch('/api/start-authentication', {
method: 'POST',
body: JSON.stringify({ username })
}).then(r => r.json());
const result = await sdk.authenticate({ username, ...challenge });
if (result.success) {
const user = await fetch('/api/complete-authentication', {
method: 'POST',
body: JSON.stringify({ assertion: result.assertion, username })
}).then(r => r.json());
return user;
} else {
throw new Error(result.error);
}
} catch (err: any) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, [sdk]);
return { authenticate, loading, error };
};
// Usage in component
const LoginButton = () => {
const { authenticate, loading, error } = usePasskeyAuth();
const handleLogin = async () => {
try {
const user = await authenticate();
console.log('Logged in:', user);
} catch (error) {
console.error('Login failed:', error);
}
};
return (
<div>
<button onClick={handleLogin} disabled={loading}>
{loading ? 'Authenticating...' : '🔐 Login with Passkey'}
</button>
{error && <div className="error">{error}</div>}
</div>
);
};
🔍 Troubleshooting
Common Issues
WebAuthn not supported:
if (!sdk.isSupported()) {
console.log('WebAuthn not supported, showing alternative login');
showPasswordLogin();
}
No platform authenticator:
const available = await sdk.isPlatformAuthenticatorAvailable();
if (!available) {
console.log('No biometric authenticator available');
// Show QR code for cross-device authentication
}
Registration conflicts:
// Handle existing credential errors
try {
await sdk.register(options);
} catch (error) {
if (error.name === 'InvalidStateError') {
console.log('Credential already exists for this user');
// Proceed to authentication instead
}
}
📖 Next Steps
- iOS SDK - Native iOS implementation
- Android SDK - Native Android implementation
- Ionic Plugin - Cross-platform mobile
- API Reference - Direct API integration
Need Higher-Level Integration?
Consider using React SDK or JavaScript SDK for easier integration with OAuth and hosted authentication pages.