🎯 Рекомендуемые коллекции
Балансированные коллекции примеров кода из различных категорий, которые вы можете исследовать
Беспарольная аутентификация WebAuthn
Примеры Web Authentication API с биометрической аутентификацией, ключами безопасности и системами входа без пароля
💻 WebAuthn Hello World javascript
🟢 simple
⭐⭐
Базовая реализация WebAuthn с простым потоком регистрации и аутентификации
⏱️ 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
💻 Система Управления Passkeys typescript
🟡 intermediate
⭐⭐⭐⭐
Полная система управления passkeys с поддержкой нескольких устройств, опций резервирования и удобного интерфейса
⏱️ 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
💻 Интеграция MFA typescript
🔴 complex
⭐⭐⭐⭐⭐
Интеграция WebAuthn с существующими MFA системами, резервными кодами и опциями восстановления
⏱️ 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