diff --git a/pages/registrieren.vue b/pages/registrieren.vue index a4abb72..70e2bf2 100644 --- a/pages/registrieren.vue +++ b/pages/registrieren.vue @@ -334,7 +334,19 @@ const handleRegisterWithPasskey = async () => { } }) + if (!pre.success || !pre.options) { + throw new Error('Ungültige Antwort vom Server') + } + + // Debug: Prüfe Options-Struktur + if (!pre.options.challenge) { + console.error('Options fehlen challenge:', pre.options) + throw new Error('Ungültige WebAuthn-Options vom Server') + } + const mod = await import('@simplewebauthn/browser') + // startRegistration erwartet die Options direkt, nicht verschachtelt + // @simplewebauthn/browser v13+ erwartet die Options direkt const credential = await mod.startRegistration(pre.options) const response = await $fetch('/api/auth/register-passkey', { diff --git a/server/api/auth/register-passkey-options.post.js b/server/api/auth/register-passkey-options.post.js index 5357df1..f464f92 100644 --- a/server/api/auth/register-passkey-options.post.js +++ b/server/api/auth/register-passkey-options.post.js @@ -42,7 +42,9 @@ export default defineEventHandler(async (event) => { authenticatorSelection: { residentKey: 'preferred', userVerification: 'preferred' - } + }, + // Timeout erhöhen für Cross-Device (Standard: 60s, hier: 5 Minuten) + timeout: 300000 }) setPreRegistration(registrationId, { @@ -55,6 +57,27 @@ export default defineEventHandler(async (event) => { await writeAuditLog('auth.passkey.prereg.options', { email }) + // CORS-Header für Cross-Device Authentication + const requestOrigin = getHeader(event, 'origin') + if (requestOrigin) { + setHeader(event, 'Access-Control-Allow-Origin', requestOrigin) + setHeader(event, 'Access-Control-Allow-Credentials', 'true') + setHeader(event, 'Access-Control-Allow-Methods', 'POST, OPTIONS') + setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization') + } + + if (getMethod(event) === 'OPTIONS') { + return { success: true } + } + + // Debug: Log Options-Struktur + console.log('[WebAuthn Pre-Registration Options]', { + hasChallenge: !!options.challenge, + rpId: options.rp?.id, + userId: options.user?.id ? 'present' : 'missing', + timeout: options.timeout + }) + return { success: true, registrationId, options } })