diff --git a/server/api/auth/passkeys/authentication-options.post.js b/server/api/auth/passkeys/authentication-options.post.js index 63ee93f..6e55e6d 100644 --- a/server/api/auth/passkeys/authentication-options.post.js +++ b/server/api/auth/passkeys/authentication-options.post.js @@ -2,13 +2,28 @@ import { generateAuthenticationOptions } from '@simplewebauthn/server' import { getWebAuthnConfig } from '../../../utils/webauthn-config.js' import { setAuthChallenge } from '../../../utils/webauthn-challenges.js' -export default defineEventHandler(async (_event) => { +export default defineEventHandler(async (event) => { + // CORS-Header für Cross-Device Authentication + const origin = getHeader(event, 'origin') + if (origin) { + setHeader(event, 'Access-Control-Allow-Origin', origin) + 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 } + } + const { rpId } = getWebAuthnConfig() // Username-less / discoverable credentials: allowCredentials absichtlich leer const options = await generateAuthenticationOptions({ rpID: rpId, - userVerification: 'preferred' + userVerification: 'preferred', + // Timeout erhöhen für Cross-Device (Standard: 60s, hier: 5 Minuten) + timeout: 300000 }) setAuthChallenge(options.challenge) diff --git a/server/api/auth/passkeys/login.post.js b/server/api/auth/passkeys/login.post.js index 3316d1a..8295077 100644 --- a/server/api/auth/passkeys/login.post.js +++ b/server/api/auth/passkeys/login.post.js @@ -18,6 +18,19 @@ function findUserByCredentialId(users, credentialId) { } export default defineEventHandler(async (event) => { + // CORS-Header für Cross-Device Authentication + const origin = getHeader(event, 'origin') + if (origin) { + setHeader(event, 'Access-Control-Allow-Origin', origin) + 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 } + } + const ip = getClientIp(event) const body = await readBody(event) const response = body?.credential diff --git a/server/api/auth/passkeys/register.post.js b/server/api/auth/passkeys/register.post.js index 2c49a01..4ebc0ee 100644 --- a/server/api/auth/passkeys/register.post.js +++ b/server/api/auth/passkeys/register.post.js @@ -6,6 +6,19 @@ import { toBase64Url } from '../../../utils/webauthn-encoding.js' import { writeAuditLog } from '../../../utils/audit-log.js' export default defineEventHandler(async (event) => { + // CORS-Header für Cross-Device Authentication + const origin = getHeader(event, 'origin') + if (origin) { + setHeader(event, 'Access-Control-Allow-Origin', origin) + 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 } + } + const token = getCookie(event, 'auth_token') const user = token ? await getUserFromToken(token) : null diff --git a/server/api/auth/passkeys/registration-options.post.js b/server/api/auth/passkeys/registration-options.post.js index 88bf970..284dce8 100644 --- a/server/api/auth/passkeys/registration-options.post.js +++ b/server/api/auth/passkeys/registration-options.post.js @@ -5,6 +5,19 @@ import { setRegistrationChallenge } from '../../../utils/webauthn-challenges.js' import { writeAuditLog } from '../../../utils/audit-log.js' export default defineEventHandler(async (event) => { + // CORS-Header für Cross-Device Authentication + const origin = getHeader(event, 'origin') + if (origin) { + setHeader(event, 'Access-Control-Allow-Origin', origin) + 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 } + } + const token = getCookie(event, 'auth_token') const user = token ? await getUserFromToken(token) : null @@ -17,7 +30,16 @@ export default defineEventHandler(async (event) => { throw createError({ statusCode: 403, statusMessage: 'Keine Berechtigung' }) } - const { rpId, rpName } = getWebAuthnConfig() + const { rpId, rpName, origin: webauthnOrigin } = getWebAuthnConfig() + + // Debug: Log für Cross-Device Troubleshooting + const requestOrigin = getHeader(event, 'origin') + console.log('[WebAuthn Registration]', { + rpId, + webauthnOrigin, + requestOrigin, + userAgent: getHeader(event, 'user-agent') + }) const existing = Array.isArray(user.passkeys) ? user.passkeys : [] const excludeCredentials = existing @@ -39,8 +61,11 @@ export default defineEventHandler(async (event) => { authenticatorSelection: { residentKey: 'preferred', userVerification: 'preferred' + // authenticatorAttachment weglassen = beide Typen erlauben (platform + cross-platform) }, - excludeCredentials + excludeCredentials, + // Timeout erhöhen für Cross-Device (Standard: 60s, hier: 5 Minuten) + timeout: 300000 }) setRegistrationChallenge(user.id, options.challenge)