🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
Authentification sans Mot de Passe WebAuthn
Exemples de Web Authentication API avec authentification biométrique, clés de sécurité et systèmes de connexion sans mot de passe
💻 WebAuthn Hello World javascript
🟢 simple
⭐⭐
Implémentation WebAuthn de base avec flux simple d'inscription et d'authentification
⏱️ 20 min
🏷️ webauthn, authentication, security, passwordless
Prerequisites:
JavaScript basics, Async/await, Cryptography basics
// WebAuthn Hello World - Basic Passwordless Authentication
// Simple implementation of registration and authentication
class WebAuthnHelloWorld {
constructor() {
this.challenges = new Map() // Store challenges for verification
this.credentials = new Map() // In-memory credential storage (use DB in production)
}
// Generate random challenge
generateChallenge() {
const array = new Uint8Array(32)
crypto.getRandomValues(array)
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('')
}
// Convert ArrayBuffer to Base64URL
arrayBufferToBase64URL(buffer) {
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '')
}
// Convert Base64URL to ArrayBuffer
base64URLToArrayBuffer(base64url) {
return Uint8Array.from(atob(base64url.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)).buffer
}
// Register new credential
async register(username, displayName = username) {
try {
// Generate challenge
const challenge = this.generateChallenge()
this.challenges.set(username, challenge)
// Create credential creation options
const credentialCreationOptions = {
challenge: this.base64URLToArrayBuffer(challenge),
rp: {
name: "WebAuthn Demo",
id: window.location.hostname
},
user: {
id: this.base64URLToArrayBuffer(btoa(username)),
name: username,
displayName: displayName
},
pubKeyCredParams: [
{ alg: -7, type: "public-key" }, // ES256
{ alg: -257, type: "public-key" } // RS256
],
authenticatorSelection: {
authenticatorAttachment: "platform", // Use device built-in authenticator
userVerification: "required"
},
timeout: 60000,
attestation: "direct"
}
console.log('Creating credential with options:', credentialCreationOptions)
// Create credential
const credential = await navigator.credentials.create({
publicKey: credentialCreationOptions
})
if (!credential) {
throw new Error('Credential creation failed')
}
// Store credential (in production, save to database)
const credentialData = {
id: this.arrayBufferToBase64URL(credential.rawId),
type: credential.type,
response: {
attestationObject: this.arrayBufferToBase64URL(credential.response.attestationObject),
clientDataJSON: this.arrayBufferToBase64URL(credential.response.clientDataJSON)
}
}
this.credentials.set(username, credentialData)
console.log('Credential registered successfully:', {
username,
credentialId: credentialData.id
})
return {
success: true,
credentialId: credentialData.id,
message: 'Registration successful!'
}
} catch (error) {
console.error('Registration error:', error)
return {
success: false,
error: error.message || 'Registration failed'
}
}
}
// Authenticate user
async authenticate(username) {
try {
// Get stored credential
const storedCredential = this.credentials.get(username)
if (!storedCredential) {
throw new Error('No credential found for this user')
}
// Generate challenge
const challenge = this.generateChallenge()
this.challenges.set(username, challenge)
// Create assertion options
const assertionOptions = {
challenge: this.base64URLToArrayBuffer(challenge),
allowCredentials: [{
id: this.base64URLToArrayBuffer(storedCredential.id),
type: storedCredential.type,
transports: ['internal', 'usb', 'ble', 'nfc']
}],
userVerification: "required",
timeout: 60000
}
console.log('Authenticating with options:', assertionOptions)
// Get assertion
const assertion = await navigator.credentials.get({
publicKey: assertionOptions
})
if (!assertion) {
throw new Error('Authentication failed')
}
// Verify assertion (simplified - in production, verify cryptographic signature)
const assertionData = {
credentialId: this.arrayBufferToBase64URL(assertion.rawId),
response: {
authenticatorData: this.arrayBufferToBase64URL(assertion.response.authenticatorData),
clientDataJSON: this.arrayBufferToBase64URL(assertion.response.clientDataJSON),
signature: this.arrayBufferToBase64URL(assertion.response.signature),
userHandle: assertion.response.userHandle ?
this.arrayBufferToBase64URL(assertion.response.userHandle) : null
}
}
console.log('Authentication successful:', {
username,
credentialId: assertionData.credentialId
})
return {
success: true,
credentialId: assertionData.credentialId,
message: 'Authentication successful!'
}
} catch (error) {
console.error('Authentication error:', error)
return {
success: false,
error: error.message || 'Authentication failed'
}
}
}
// Check if WebAuthn is supported
static isSupported() {
return !!(navigator.credentials && navigator.credentials.create && navigator.credentials.get)
}
// Check if platform authenticator is available
static async isPlatformAuthenticatorAvailable() {
if (!this.isSupported()) return false
try {
const available = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
return available
} catch (error) {
console.warn('Error checking platform authenticator:', error)
return false
}
}
}
// HTML UI Example
// <!DOCTYPE html>
// <html>
// <head>
// <title>WebAuthn Hello World</title>
// <style>
// body {
// font-family: system-ui, -apple-system, sans-serif;
// max-width: 500px;
// margin: 50px auto;
// padding: 20px;
// background: #f5f5f5;
// }
// .container {
// background: white;
// padding: 30px;
// border-radius: 10px;
// box-shadow: 0 2px 10px rgba(0,0,0,0.1);
// }
// h1 {
// text-align: center;
// color: #333;
// margin-bottom: 30px;
// }
// .form-group {
// margin-bottom: 20px;
// }
// label {
// display: block;
// margin-bottom: 5px;
// font-weight: 500;
// color: #555;
// }
// input {
// width: 100%;
// padding: 12px;
// border: 1px solid #ddd;
// border-radius: 5px;
// font-size: 16px;
// box-sizing: border-box;
// }
// button {
// width: 100%;
// padding: 12px;
// background: #007AFF;
// color: white;
// border: none;
// border-radius: 5px;
// font-size: 16px;
// cursor: pointer;
// margin-bottom: 10px;
// }
// button:hover {
// background: #0056b3;
// }
// button:disabled {
// background: #ccc;
// cursor: not-allowed;
// }
// .status {
// padding: 15px;
// border-radius: 5px;
// margin-top: 20px;
// text-align: center;
// }
// .success {
// background: #d4edda;
// color: #155724;
// border: 1px solid #c3e6cb;
// }
// .error {
// background: #f8d7da;
// color: #721c24;
// border: 1px solid #f5c6cb;
// }
// .info {
// background: #d1ecf1;
// color: #0c5460;
// border: 1px solid #bee5eb;
// }
// </style>
// </head>
// <body>
// <div class="container">
// <h1>🔐 WebAuthn Hello World</h1>
// <div class="form-group">
// <label for="username">Username:</label>
// <input type="text" id="username" placeholder="Enter your username" value="alice">
// </div>
// <button id="registerBtn" onclick="register()">Register New Credential</button>
// <button id="authenticateBtn" onclick="authenticate()">Sign In</button>
// <div id="status" class="status info">
// Click "Register" to create a new credential, then "Sign In" to authenticate
// </div>
// </div>
// <script src="webauthn-hello-world.js"></script>
// <script>
// const webauthn = new WebAuthnHelloWorld()
// // Check browser support
// if (!WebAuthnHelloWorld.isSupported()) {
// updateStatus('WebAuthn is not supported in this browser', 'error')
// document.getElementById('registerBtn').disabled = true
// document.getElementById('authenticateBtn').disabled = true
// }
// async function register() {
// const username = document.getElementById('username').value.trim()
// if (!username) {
// updateStatus('Please enter a username', 'error')
// return
// }
// updateStatus('Creating credential...', 'info')
// disableButtons(true)
// try {
// const result = await webauthn.register(username)
// if (result.success) {
// updateStatus(`✅ ${result.message} (ID: ${result.credentialId})`, 'success')
// } else {
// updateStatus(`❌ Registration failed: ${result.error}`, 'error')
// }
// } catch (error) {
// updateStatus(`❌ Registration error: ${error.message}`, 'error')
// } finally {
// disableButtons(false)
// }
// }
// async function authenticate() {
// const username = document.getElementById('username').value.trim()
// if (!username) {
// updateStatus('Please enter a username', 'error')
// return
// }
// updateStatus('Authenticating...', 'info')
// disableButtons(true)
// try {
// const result = await webauthn.authenticate(username)
// if (result.success) {
// updateStatus(`✅ ${result.message} (ID: ${result.credentialId})`, 'success')
// // Redirect to dashboard on success
// setTimeout(() => {
// updateStatus('🎉 Authentication successful! Redirecting...', 'success')
// }, 2000)
// } else {
// updateStatus(`❌ Authentication failed: ${result.error}`, 'error')
// }
// } catch (error) {
// updateStatus(`❌ Authentication error: ${error.message}`, 'error')
// } finally {
// disableButtons(false)
// }
// }
// function updateStatus(message, type) {
// const statusEl = document.getElementById('status')
// statusEl.textContent = message
// statusEl.className = `status ${type}`
// }
// function disableButtons(disabled) {
// document.getElementById('registerBtn').disabled = disabled
// document.getElementById('authenticateBtn').disabled = disabled
// }
// // Check platform authenticator availability
// (async () => {
// const isAvailable = await WebAuthnHelloWorld.isPlatformAuthenticatorAvailable()
// if (isAvailable) {
// console.log('✅ Platform authenticator (biometric) is available')
// } else {
// console.log('ℹ️ Platform authenticator not available, but you can use security keys')
// }
// })()
// </script>
// </body>
// </html>
// Usage example:
// const webauthn = new WebAuthnHelloWorld()
//
// // Register a new user
// webauthn.register('alice', 'Alice Smith')
// .then(result => console.log(result))
//
// // Authenticate existing user
// webauthn.authenticate('alice')
// .then(result => console.log(result))
export default WebAuthnHelloWorld
💻 Système de Gestion Passkeys typescript
🟡 intermediate
⭐⭐⭐⭐
Système complet de gestion passkeys avec support multi-appareils, options de sauvegarde et interface conviviale
⏱️ 35 min
🏷️ webauthn, passkeys, authentication, security, passwordless
Prerequisites:
TypeScript, IndexedDB, Async programming, Cryptography
// WebAuthn Passkeys Manager - Complete Passwordless Authentication System
// Multiple device support, backup options, and enterprise features
interface PasskeyCredential {
id: string
name: string
createdAt: Date
lastUsedAt?: Date
deviceType: 'platform' | 'cross-platform'
userHandle: string
publicKey: string
counter: number
transports: string[]
backupEligible: boolean
backupState: boolean
}
interface UserAccount {
id: string
username: string
email: string
displayName: string
passkeys: PasskeyCredential[]
createdAt: Date
lastLoginAt?: Date
mfaEnabled: boolean
}
class PasskeysManager {
private db: IDBDatabase | null = null
private readonly DB_NAME = 'PasskeysManagerDB'
private readonly DB_VERSION = 1
private readonly STORE_NAME = 'users'
constructor() {
this.initDB()
}
// Initialize IndexedDB for credential storage
private async initDB(): Promise<void> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.DB_NAME, this.DB_VERSION)
request.onerror = () => reject(request.error)
request.onsuccess = () => {
this.db = request.result
resolve()
}
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result
if (!db.objectStoreNames.contains(this.STORE_NAME)) {
const store = db.createObjectStore(this.STORE_NAME, { keyPath: 'id' })
store.createIndex('username', 'username', { unique: true })
store.createIndex('email', 'email', { unique: true })
}
}
})
}
// Generate secure random challenge
private generateChallenge(): string {
const array = new Uint8Array(32)
crypto.getRandomValues(array)
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('')
}
// Convert ArrayBuffer to Base64URL
private arrayBufferToBase64URL(buffer: ArrayBuffer): string {
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '')
}
// Convert Base64URL to ArrayBuffer
private base64URLToArrayBuffer(base64url: string): ArrayBuffer {
return Uint8Array.from(atob(base64url.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)).buffer
}
// Generate unique user ID
private generateUserId(): string {
return this.arrayBufferToBase64URL(crypto.getRandomValues(new Uint8Array(16)))
}
// Create new user account
async createUser(username: string, email: string, displayName: string): Promise<UserAccount> {
if (!this.db) throw new Error('Database not initialized')
// Check if user already exists
const existingUser = await this.getUserByUsername(username)
if (existingUser) {
throw new Error('User already exists')
}
const user: UserAccount = {
id: this.generateUserId(),
username,
email,
displayName,
passkeys: [],
createdAt: new Date(),
mfaEnabled: false
}
await this.saveUser(user)
return user
}
// Register new passkey for user
async registerPasskey(
userId: string,
passkeyName: string,
authenticatorAttachment: 'platform' | 'cross-platform' = 'platform'
): Promise<PasskeyCredential> {
if (!this.db) throw new Error('Database not initialized')
const user = await this.getUserById(userId)
if (!user) {
throw new Error('User not found')
}
try {
// Generate challenge
const challenge = this.generateChallenge()
// Create credential creation options
const credentialCreationOptions: CredentialCreationOptions = {
challenge: this.base64URLToArrayBuffer(challenge),
rp: {
name: "Passkeys Manager",
id: window.location.hostname
},
user: {
id: this.base64URLToArrayBuffer(user.id),
name: user.username,
displayName: user.displayName
},
pubKeyCredParams: [
{ alg: -7, type: "public-key" }, // ES256
{ alg: -257, type: "public-key" }, // RS256
{ alg: -8, type: "public-key" }, // Ed25519
],
authenticatorSelection: {
authenticatorAttachment,
residentKey: 'required',
userVerification: 'required'
},
extensions: {
credProps: true,
largeBlob: true
},
timeout: 60000,
attestation: 'direct'
}
// Create credential
const credential = await navigator.credentials.create({
publicKey: credentialCreationOptions
}) as PublicKeyCredential
if (!credential) {
throw new Error('Credential creation failed')
}
// Parse client data
const clientDataJSON = JSON.parse(
new TextDecoder().decode(credential.response.clientDataJSON)
)
// Extract authenticator info
const authData = new Uint8Array(credential.response.authenticatorData)
const flags = authData[32]
const backupEligible = !!(flags & 0x08)
const backupState = !!(flags & 0x10)
// Create passkey record
const passkey: PasskeyCredential = {
id: this.arrayBufferToBase64URL(credential.rawId),
name: passkeyName,
createdAt: new Date(),
deviceType: authenticatorAttachment,
userHandle: user.id,
publicKey: this.arrayBufferToBase64URL(credential.response.publicKey || new ArrayBuffer(0)),
counter: 0,
transports: clientDataJSON.transports || [],
backupEligible,
backupState
}
// Add passkey to user
user.passkeys.push(passkey)
await this.saveUser(user)
console.log('Passkey registered successfully:', {
name: passkeyName,
id: passkey.id,
deviceType: passkey.deviceType,
backupEligible: passkey.backupEligible
})
return passkey
} catch (error) {
console.error('Passkey registration error:', error)
throw new Error(`Passkey registration failed: ${error.message}`)
}
}
// Authenticate user with passkey
async authenticate(userHint?: string): Promise<{ user: UserAccount; credentialId: string }> {
if (!this.db) throw new Error('Database not initialized')
try {
// Generate challenge
const challenge = this.generateChallenge()
// Get all available passkeys if no specific user hint
let allowCredentials: PublicKeyCredentialDescriptor[] = []
if (userHint) {
const user = await this.getUserByUsername(userHint)
if (user && user.passkeys.length > 0) {
allowCredentials = user.passkeys.map(passkey => ({
id: this.base64URLToArrayBuffer(passkey.id),
type: 'public-key',
transports: passkey.transports as AuthenticatorTransport[]
}))
}
}
// Create assertion options
const assertionOptions: AssertionOptions = {
challenge: this.base64URLToArrayBuffer(challenge),
allowCredentials,
userVerification: 'required',
extensions: {
largeBlob: true
},
timeout: 60000
}
// Get assertion
const assertion = await navigator.credentials.get({
publicKey: assertionOptions
}) as PublicKeyCredential
if (!assertion) {
throw new Error('Authentication failed')
}
// Find user by credential ID
const credentialId = this.arrayBufferToBase64URL(assertion.rawId)
let authenticatedUser: UserAccount | null = null
let matchedPasskey: PasskeyCredential | null = null
// Search all users for matching credential
for (const user of await this.getAllUsers()) {
const passkey = user.passkeys.find(pk => pk.id === credentialId)
if (passkey) {
authenticatedUser = user
matchedPasskey = passkey
break
}
}
if (!authenticatedUser || !matchedPasskey) {
throw new Error('Invalid credential')
}
// Update last used timestamp
matchedPasskey.lastUsedAt = new Date()
authenticatedUser.lastLoginAt = new Date()
await this.saveUser(authenticatedUser)
console.log('Authentication successful:', {
user: authenticatedUser.username,
credentialId,
passkeyName: matchedPasskey.name
})
return { user: authenticatedUser, credentialId }
} catch (error) {
console.error('Authentication error:', error)
throw new Error(`Authentication failed: ${error.message}`)
}
}
// List user's passkeys
async listPasskeys(userId: string): Promise<PasskeyCredential[]> {
const user = await this.getUserById(userId)
return user ? user.passkeys : []
}
// Delete passkey
async deletePasskey(userId: string, passkeyId: string): Promise<void> {
const user = await this.getUserById(userId)
if (!user) {
throw new Error('User not found')
}
const passkeyIndex = user.passkeys.findIndex(pk => pk.id === passkeyId)
if (passkeyIndex === -1) {
throw new Error('Passkey not found')
}
// Remove passkey
user.passkeys.splice(passkeyIndex, 1)
await this.saveUser(user)
}
// Update passkey name
async updatePasskeyName(userId: string, passkeyId: string, newName: string): Promise<void> {
const user = await this.getUserById(userId)
if (!user) {
throw new Error('User not found')
}
const passkey = user.passkeys.find(pk => pk.id === passkeyId)
if (!passkey) {
throw new Error('Passkey not found')
}
passkey.name = newName
await this.saveUser(user)
}
// Database operations
private async saveUser(user: UserAccount): Promise<void> {
if (!this.db) throw new Error('Database not initialized')
return new Promise((resolve, reject) => {
const transaction = this.db!.transaction([this.STORE_NAME], 'readwrite')
const store = transaction.objectStore(this.STORE_NAME)
const request = store.put(user)
request.onsuccess = () => resolve()
request.onerror = () => reject(request.error)
})
}
private async getUserById(userId: string): Promise<UserAccount | null> {
if (!this.db) throw new Error('Database not initialized')
return new Promise((resolve, reject) => {
const transaction = this.db!.transaction([this.STORE_NAME], 'readonly')
const store = transaction.objectStore(this.STORE_NAME)
const request = store.get(userId)
request.onsuccess = () => resolve(request.result || null)
request.onerror = () => reject(request.error)
})
}
private async getUserByUsername(username: string): Promise<UserAccount | null> {
if (!this.db) throw new Error('Database not initialized')
return new Promise((resolve, reject) => {
const transaction = this.db!.transaction([this.STORE_NAME], 'readonly')
const store = transaction.objectStore(this.STORE_NAME)
const index = store.index('username')
const request = index.get(username)
request.onsuccess = () => resolve(request.result || null)
request.onerror = () => reject(request.error)
})
}
private async getAllUsers(): Promise<UserAccount[]> {
if (!this.db) throw new Error('Database not initialized')
return new Promise((resolve, reject) => {
const transaction = this.db!.transaction([this.STORE_NAME], 'readonly')
const store = transaction.objectStore(this.STORE_NAME)
const request = store.getAll()
request.onsuccess = () => resolve(request.result || [])
request.onerror = () => reject(request.error)
})
}
// Export passkeys for backup
async exportPasskeys(userId: string): Promise<string> {
const user = await this.getUserById(userId)
if (!user) {
throw new Error('User not found')
}
// Create export data (excluding sensitive fields)
const exportData = {
user: {
id: user.id,
username: user.username,
email: user.email,
displayName: user.displayName
},
passkeys: user.passkeys.map(pk => ({
id: pk.id,
name: pk.name,
createdAt: pk.createdAt,
deviceType: pk.deviceType,
transports: pk.transports,
backupEligible: pk.backupEligible
})),
exportedAt: new Date().toISOString()
}
return JSON.stringify(exportData, null, 2)
}
// Check browser compatibility
static checkCompatibility(): {
supported: boolean
platformAuthenticator: boolean
conditionalUI: boolean
largeBlob: boolean
} {
return {
supported: !!(navigator.credentials && navigator.credentials.create && navigator.credentials.get),
platformAuthenticator: !!(PublicKeyCredential as any).isUserVerifyingPlatformAuthenticatorAvailable,
conditionalUI: !!(PublicKeyCredential as any).isConditionalMediationAvailable,
largeBlob: 'publicKey' in PublicKeyCredential.prototype
}
}
}
// React Component Example
// import React, { useState, useEffect } from 'react'
// const PasskeysManager: React.FC = () => {
// const [passkeysManager] = useState(() => new PasskeysManager())
// const [currentUser, setCurrentUser] = useState<UserAccount | null>(null)
// const [passkeys, setPasskeys] = useState<PasskeyCredential[]>([])
// const [loading, setLoading] = useState(false)
// const [error, setError] = useState<string | null>(null)
// useEffect(() => {
// const compatibility = PasskeysManager.checkCompatibility()
// console.log('WebAuthn compatibility:', compatibility)
// }, [])
// const handleRegisterPasskey = async (name: string, deviceType: 'platform' | 'cross-platform') => {
// if (!currentUser) return
// setLoading(true)
// setError(null)
// try {
// const passkey = await passkeysManager.registerPasskey(currentUser.id, name, deviceType)
// setPasskeys(prev => [...prev, passkey])
// } catch (err) {
// setError(err.message)
// } finally {
// setLoading(false)
// }
// }
// const handleAuthenticate = async (userHint?: string) => {
// setLoading(true)
// setError(null)
// try {
// const { user } = await passkeysManager.authenticate(userHint)
// setCurrentUser(user)
// const userPasskeys = await passkeysManager.listPasskeys(user.id)
// setPasskeys(userPasskeys)
// } catch (err) {
// setError(err.message)
// } finally {
// setLoading(false)
// }
// }
// return (
// <div className="passkeys-manager">
// {/* UI implementation here */}
// </div>
// )
// }
export default PasskeysManager
💻 Intégration MFA typescript
🔴 complex
⭐⭐⭐⭐⭐
Intégration de WebAuthn avec systèmes MFA existants, codes de secours et options de récupération
⏱️ 45 min
🏷️ webauthn, mfa, authentication, security, enterprise
Prerequisites:
Advanced TypeScript, Security concepts, Cryptography, Enterprise auth
// WebAuthn MFA Integration - Enhanced Security with Multiple Factors
// Backup codes, recovery options, and enterprise features
interface MFAConfig {
passkeysRequired: boolean
backupCodesEnabled: boolean
smsEnabled: boolean
emailEnabled: boolean
timeBasedOTPEnabled: boolean
rememberDevice: boolean
sessionTimeout: number
}
interface BackupCode {
id: string
code: string
usedAt?: Date
createdAt: Date
expiresAt: Date
}
interface AuthSession {
id: string
userId: string
deviceFingerprint: string
createdAt: Date
expiresAt: Date
isTrusted: boolean
lastUsedAt: Date
}
interface SecurityEvent {
id: string
userId: string
type: 'login' | 'passkey_added' | 'passkey_removed' | 'mfa_enabled' | 'suspicious_activity'
timestamp: Date
ipAddress: string
userAgent: string
success: boolean
details?: any
}
class WebAuthnMFAIntegration {
private passkeysManager: PasskeysManager
private config: MFAConfig
private mfaSessions = new Map<string, AuthSession>()
private securityEvents: SecurityEvent[] = []
constructor(config: MFAConfig) {
this.passkeysManager = new PasskeysManager()
this.config = config
}
// Enhanced authentication with MFA
async authenticateWithMFA(userHint?: string): Promise<{
user: UserAccount
requiresMFA: boolean
mfaMethods: string[]
sessionToken?: string
}> {
try {
// First attempt WebAuthn authentication
const { user, credentialId } = await this.passkeysManager.authenticate(userHint)
// Check if additional MFA is required
const requiresMFA = this.shouldRequireMFA(user)
if (!requiresMFA) {
// Create trusted session
const sessionToken = await this.createTrustedSession(user)
await this.logSecurityEvent(user.id, 'login', true, {
method: 'passkey_only',
credentialId
})
return {
user,
requiresMFA: false,
mfaMethods: [],
sessionToken
}
}
// MFA required - return available methods
const mfaMethods = this.getAvailableMFAMethods(user)
await this.logSecurityEvent(user.id, 'login', false, {
method: 'passkey',
requiresMFA: true,
credentialId
})
return {
user,
requiresMFA: true,
mfaMethods,
sessionToken: this.createTemporaryMFASession(user)
}
} catch (error) {
// Log failed authentication attempt
await this.logSecurityEvent('', 'login', false, {
error: error.message,
userHint
})
throw error
}
}
// Complete MFA verification
async completeMFA(
tempSessionToken: string,
mfaMethod: string,
mfaData: any
): Promise<{ user: UserAccount; sessionToken: string }> {
const tempSession = this.mfaSessions.get(tempSessionToken)
if (!tempSession || tempSession.expiresAt < new Date()) {
throw new Error('Invalid or expired MFA session')
}
const user = await this.passkeysManager['getUserById'](tempSession.userId)
if (!user) {
throw new Error('User not found')
}
let mfaVerified = false
switch (mfaMethod) {
case 'backup_code':
mfaVerified = await this.verifyBackupCode(user.id, mfaData.code)
break
case 'totp':
mfaVerified = await this.verifyTOTP(user.id, mfaData.token)
break
case 'sms':
mfaVerified = await this.verifySMS(user.id, mfaData.code)
break
case 'email':
mfaVerified = await this.verifyEmail(user.id, mfaData.code)
break
default:
throw new Error('Unsupported MFA method')
}
if (!mfaVerified) {
await this.logSecurityEvent(user.id, 'login', false, {
mfaMethod,
mfaData
})
throw new Error('MFA verification failed')
}
// Create trusted session
const sessionToken = await this.createTrustedSession(user)
// Clean up temporary session
this.mfaSessions.delete(tempSessionToken)
await this.logSecurityEvent(user.id, 'login', true, {
mfaMethod,
deviceTrusted: true
})
return { user, sessionToken }
}
// Generate backup codes
async generateBackupCodes(userId: string, count: number = 10): Promise<BackupCode[]> {
const user = await this.passkeysManager['getUserById'](userId)
if (!user) {
throw new Error('User not found')
}
const backupCodes: BackupCode[] = []
const expiresAt = new Date()
expiresAt.setFullYear(expiresAt.getFullYear() + 1) // Valid for 1 year
for (let i = 0; i < count; i++) {
const code = this.generateBackupCode()
backupCodes.push({
id: this.generateId(),
code,
createdAt: new Date(),
expiresAt
})
}
// Store backup codes (in production, encrypt these)
await this.storeBackupCodes(userId, backupCodes)
await this.logSecurityEvent(user.id, 'mfa_enabled', true, {
type: 'backup_codes_generated',
count
})
return backupCodes
}
// Setup TOTP (Time-based One-Time Password)
async setupTOTP(userId: string): Promise<{
secret: string
qrCodeUrl: string
backupCodes: BackupCode[]
}> {
const user = await this.passkeysManager['getUserById'](userId)
if (!user) {
throw new Error('User not found')
}
// Generate TOTP secret
const secret = this.generateTOTPSecret()
const issuer = encodeURIComponent('Passkeys Manager')
const accountName = encodeURIComponent(user.email)
const qrCodeUrl = `otpauth://totp/${issuer}:${accountName}?secret=${secret}&issuer=${issuer}`
// Generate backup codes
const backupCodes = await this.generateBackupCodes(userId)
// Store TOTP secret (in production, encrypt this)
await this.storeTOTPSecret(userId, secret)
await this.logSecurityEvent(user.id, 'mfa_enabled', true, {
type: 'totp_setup'
})
return { secret, qrCodeUrl, backupCodes }
}
// Verify TOTP token
async verifyTOTP(userId: string, token: string): Promise<boolean> {
const secret = await this.getTOTPSecret(userId)
if (!secret) {
return false
}
// Verify token (simplified - use proper TOTP library in production)
const expectedToken = this.generateTOTPToken(secret)
const isValid = token === expectedToken
if (isValid) {
await this.logSecurityEvent(userId, 'login', true, {
mfaMethod: 'totp',
tokenVerified: true
})
}
return isValid
}
// Check if MFA is required
private shouldRequireMFA(user: UserAccount): boolean {
if (!this.config.passkeysRequired) return false
// Always require MFA if user has it enabled
if (user.mfaEnabled) return true
// Require MFA for new devices or suspicious locations
// This would include IP geolocation, device fingerprinting, etc.
return this.isNewDevice() || this.isSuspiciousLocation()
}
// Get available MFA methods for user
private getAvailableMFAMethods(user: UserAccount): string[] {
const methods = []
if (this.config.backupCodesEnabled) {
methods.push('backup_code')
}
if (this.config.timeBasedOTPEnabled) {
// Check if user has TOTP setup
if (this.hasTOTPSetup(user.id)) {
methods.push('totp')
}
}
if (this.config.smsEnabled && user.email) {
methods.push('sms')
}
if (this.config.emailEnabled && user.email) {
methods.push('email')
}
return methods
}
// Create trusted session
private async createTrustedSession(user: UserAccount): Promise<string> {
const session: AuthSession = {
id: this.generateId(),
userId: user.id,
deviceFingerprint: this.generateDeviceFingerprint(),
createdAt: new Date(),
expiresAt: new Date(Date.now() + this.config.sessionTimeout),
isTrusted: true,
lastUsedAt: new Date()
}
// Store session (in production, use secure session storage)
await this.storeSession(session)
return session.id
}
// Create temporary MFA session
private createTemporaryMFASession(user: UserAccount): string {
const session: AuthSession = {
id: this.generateId(),
userId: user.id,
deviceFingerprint: this.generateDeviceFingerprint(),
createdAt: new Date(),
expiresAt: new Date(Date.now() + 10 * 60 * 1000), // 10 minutes
isTrusted: false,
lastUsedAt: new Date()
}
this.mfaSessions.set(session.id, session)
return session.id
}
// Utility methods
private generateBackupCode(): string {
const characters = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' // Remove confusing characters
let code = ''
for (let i = 0; i < 8; i++) {
if (i > 0 && i % 4 === 0) code += '-'
code += characters[Math.floor(Math.random() * characters.length)]
}
return code
}
private generateTOTPSecret(): string {
const base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
let secret = ''
for (let i = 0; i < 32; i++) {
secret += base32Chars[Math.floor(Math.random() * base32Chars.length)]
}
return secret
}
private generateTOTPToken(secret: string): string {
// Simplified TOTP generation - use proper library in production
const timestamp = Math.floor(Date.now() / 30000) // 30-second intervals
return (timestamp + secret.length).toString().slice(-6)
}
private generateDeviceFingerprint(): string {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (ctx) {
ctx.textBaseline = 'top'
ctx.font = '14px Arial'
ctx.fillText('Device fingerprint', 2, 2)
return canvas.toDataURL().slice(-20)
}
return Math.random().toString(36).substring(2)
}
private generateId(): string {
return Math.random().toString(36).substring(2) + Date.now().toString(36)
}
private isNewDevice(): boolean {
// Implement device fingerprinting logic
return Math.random() > 0.7 // 30% chance of new device for demo
}
private isSuspiciousLocation(): boolean {
// Implement geolocation checking
return Math.random() > 0.9 // 10% chance of suspicious location for demo
}
// Security logging
private async logSecurityEvent(
userId: string,
type: SecurityEvent['type'],
success: boolean,
details?: any
): Promise<void> {
const event: SecurityEvent = {
id: this.generateId(),
userId,
type,
timestamp: new Date(),
ipAddress: '192.168.1.1', // Get from request in production
userAgent: navigator.userAgent,
success,
details
}
this.securityEvents.push(event)
// In production, store in secure logging system
console.log('Security event:', event)
// Alert on suspicious activity
if (!success || type === 'suspicious_activity') {
await this.alertSuspiciousActivity(event)
}
}
private async alertSuspiciousActivity(event: SecurityEvent): Promise<void> {
// Implement alert system (email, SMS, admin notification)
console.warn('🚨 Suspicious activity detected:', event)
}
// Storage methods (implement with your preferred storage)
private async storeBackupCodes(userId: string, codes: BackupCode[]): Promise<void> {
// Implement secure storage
}
private async storeTOTPSecret(userId: string, secret: string): Promise<void> {
// Implement secure storage
}
private async getTOTPSecret(userId: string): Promise<string | null> {
// Implement secure retrieval
return null
}
private async hasTOTPSetup(userId: string): Promise<boolean> {
// Check if TOTP is configured for user
return false
}
private async storeSession(session: AuthSession): Promise<void> {
// Implement secure session storage
}
// Enterprise features
async getSecurityReport(userId: string): Promise<{
user: UserAccount
recentEvents: SecurityEvent[]
trustedDevices: AuthSession[]
securityScore: number
recommendations: string[]
}> {
const user = await this.passkeysManager['getUserById'](userId)
if (!user) {
throw new Error('User not found')
}
const recentEvents = this.securityEvents
.filter(event => event.userId === userId)
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
.slice(0, 50)
const trustedDevices = Array.from(this.mfaSessions.values())
.filter(session => session.userId === userId && session.isTrusted)
const securityScore = this.calculateSecurityScore(user, recentEvents)
const recommendations = this.generateSecurityRecommendations(user, securityScore)
return {
user,
recentEvents,
trustedDevices,
securityScore,
recommendations
}
}
private calculateSecurityScore(user: UserAccount, events: SecurityEvent[]): number {
let score = 50 // Base score
// Add points for passkeys
score += Math.min(user.passkeys.length * 10, 30)
// Add points for MFA enabled
if (user.mfaEnabled) score += 20
// Subtract points for failed login attempts
const failedLogins = events.filter(e => e.type === 'login' && !e.success).length
score -= Math.min(failedLogins * 5, 20)
return Math.max(0, Math.min(100, score))
}
private generateSecurityRecommendations(user: UserAccount, score: number): string[] {
const recommendations = []
if (user.passkeys.length === 0) {
recommendations.push('Add at least one passkey for passwordless authentication')
}
if (user.passkeys.length === 1) {
recommendations.push('Add a backup passkey for account recovery')
}
if (!user.mfaEnabled) {
recommendations.push('Enable multi-factor authentication for enhanced security')
}
if (score < 70) {
recommendations.push('Review recent security events for suspicious activity')
}
return recommendations
}
}
// Usage Example
// const mfaConfig: MFAConfig = {
// passkeysRequired: true,
// backupCodesEnabled: true,
// smsEnabled: true,
// emailEnabled: true,
// timeBasedOTPEnabled: true,
// rememberDevice: true,
// sessionTimeout: 24 * 60 * 60 * 1000 // 24 hours
// }
//
// const mfaIntegration = new WebAuthnMFAIntegration(mfaConfig)
//
// // Authenticate with MFA
// const authResult = await mfaIntegration.authenticateWithMFA('[email protected]')
// if (authResult.requiresMFA) {
// // Show MFA options UI
// console.log('Available MFA methods:', authResult.mfaMethods)
//
// // Complete MFA (user enters backup code)
// const finalResult = await mfaIntegration.completeMFA(
// authResult.sessionToken!,
// 'backup_code',
// { code: 'ABCD-1234-EFGH' }
// )
// console.log('Authentication complete:', finalResult)
// }
export default WebAuthnMFAIntegration