diff --git a/CORS_TEST_ANLEITUNG.md b/CORS_TEST_ANLEITUNG.md new file mode 100644 index 0000000..f32bcb2 --- /dev/null +++ b/CORS_TEST_ANLEITUNG.md @@ -0,0 +1,124 @@ +# CORS-Test für Passkey Cross-Device Authentication + +## Test-Datei verwenden + +1. **Öffne die Test-Seite:** + ``` + https://harheimertc.tsschulz.de/test-cors.html + ``` + +2. **Führe die Tests aus:** + - Klicke auf "Test OPTIONS Request" - sollte Status 204 zurückgeben + - Klicke auf "Test POST Request" - sollte Status 200 zurückgeben + - Klicke auf "Test /api/auth/register-passkey-options" - sollte Options zurückgeben + +3. **Prüfe die Browser-Entwicklertools:** + - Öffne F12 → Network Tab + - Führe die Tests aus + - Klicke auf jeden Request und prüfe die **Response Headers**: + - `Access-Control-Allow-Origin` sollte `https://harheimertc.tsschulz.de` sein + - `Access-Control-Allow-Credentials` sollte `true` sein + - `Access-Control-Allow-Methods` sollte `GET, POST, OPTIONS` enthalten + +## Manueller Test mit curl + +### OPTIONS Preflight Test: +```bash +curl -X OPTIONS \ + -H "Origin: https://harheimertc.tsschulz.de" \ + -H "Access-Control-Request-Method: POST" \ + -H "Access-Control-Request-Headers: Content-Type" \ + -v \ + https://harheimertc.tsschulz.de/api/auth/register-passkey-options +``` + +**Erwartete Response:** +- Status: 204 No Content +- Header: `Access-Control-Allow-Origin: https://harheimertc.tsschulz.de` +- Header: `Access-Control-Allow-Credentials: true` + +### POST Request Test: +```bash +curl -X POST \ + -H "Origin: https://harheimertc.tsschulz.de" \ + -H "Content-Type: application/json" \ + -d '{"name":"Test","email":"test@example.com"}' \ + -v \ + https://harheimertc.tsschulz.de/api/auth/register-passkey-options +``` + +**Erwartete Response:** +- Status: 200 OK +- Header: `Access-Control-Allow-Origin: https://harheimertc.tsschulz.de` +- Body: JSON mit `success: true` und `options` Objekt + +## Browser Console Test + +Öffne die Browser-Konsole (F12) und führe aus: + +```javascript +// Test 1: OPTIONS Preflight +fetch('https://harheimertc.tsschulz.de/api/auth/register-passkey-options', { + method: 'OPTIONS', + headers: { + 'Origin': window.location.origin, + 'Access-Control-Request-Method': 'POST', + 'Access-Control-Request-Headers': 'Content-Type' + } +}).then(r => { + console.log('OPTIONS Status:', r.status); + console.log('CORS Headers:', { + origin: r.headers.get('Access-Control-Allow-Origin'), + credentials: r.headers.get('Access-Control-Allow-Credentials'), + methods: r.headers.get('Access-Control-Allow-Methods') + }); +}); + +// Test 2: POST Request +fetch('https://harheimertc.tsschulz.de/api/auth/register-passkey-options', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Origin': window.location.origin + }, + body: JSON.stringify({ + name: 'Test User', + email: 'test@example.com' + }) +}).then(r => r.json()).then(data => { + console.log('POST Response:', data); + console.log('Has Options:', !!data.options); + console.log('Has Challenge:', !!data.options?.challenge); +}); +``` + +## Was zu prüfen ist: + +1. **OPTIONS Request:** + - Status sollte 204 sein (nicht 200 oder 404) + - CORS-Header müssen vorhanden sein + +2. **POST Request:** + - Status sollte 200 sein + - CORS-Header müssen vorhanden sein + - Response sollte `success: true` und `options` enthalten + +3. **Cross-Device Problem:** + - Wenn CORS korrekt ist, aber Cross-Device trotzdem nicht funktioniert: + - Prüfe, ob der QR-Code die richtige Origin enthält + - Prüfe, ob das Smartphone die gleiche Origin erreichen kann + - Prüfe die Browser-Logs auf dem Smartphone (falls möglich) + +## Häufige Probleme: + +1. **Apache überschreibt CORS-Header:** + - Lösung: In Apache-Config sicherstellen, dass CORS-Header nicht überschrieben werden + +2. **OPTIONS gibt 404 zurück:** + - Lösung: Endpoint muss OPTIONS-Requests explizit behandeln + +3. **CORS-Header fehlen:** + - Lösung: Server-Endpoint muss CORS-Header setzen + +4. **Origin-Mismatch:** + - Lösung: `WEBAUTHN_ORIGIN` muss exakt mit der Browser-Origin übereinstimmen diff --git a/pages/registrieren.vue b/pages/registrieren.vue index 013132d..4af0c25 100644 --- a/pages/registrieren.vue +++ b/pages/registrieren.vue @@ -505,8 +505,7 @@ const handleRegisterWithPasskey = async () => { const webauthnStart = Date.now() const mod = await import('@simplewebauthn/browser') - // startRegistration erwartet die Options direkt - // @simplewebauthn/browser v13+ erwartet die Options direkt + // startRegistration erwartet die Options direkt (wie in anderen Dateien auch) let credential try { // Timeout-Warnung nach 2 Minuten @@ -517,29 +516,11 @@ const handleRegisterWithPasskey = async () => { console.warn('[DEBUG] Challenge:', pre.options?.challenge) }, 120000) - // Stelle sicher, dass die Options korrekt formatiert sind - // @simplewebauthn/browser v13+ erwartet die Options direkt als Objekt - const registrationOptions = { - challenge: pre.options.challenge, - rp: pre.options.rp, - user: pre.options.user, - pubKeyCredParams: pre.options.pubKeyCredParams, - timeout: pre.options.timeout, - attestation: pre.options.attestation || 'none', - excludeCredentials: pre.options.excludeCredentials || [], - authenticatorSelection: pre.options.authenticatorSelection, - extensions: pre.options.extensions || {} - } - console.log('[DEBUG] startRegistration called - QR-Code should appear now (if Cross-Device)') - console.log('[DEBUG] Registration options structure:', { - hasChallenge: !!registrationOptions.challenge, - hasRp: !!registrationOptions.rp, - hasUser: !!registrationOptions.user, - timeout: registrationOptions.timeout - }) + console.log('[DEBUG] Passing options directly to startRegistration (same as in profil.vue)') - credential = await mod.startRegistration(registrationOptions) + // Direkt die Options übergeben (wie in profil.vue und passkey-wiederherstellen.vue) + credential = await mod.startRegistration(pre.options) clearTimeout(timeoutWarning) diff --git a/public/test-cors.html b/public/test-cors.html new file mode 100644 index 0000000..089188e --- /dev/null +++ b/public/test-cors.html @@ -0,0 +1,214 @@ + + + + CORS Test für Passkey Cross-Device + + + +

CORS Test für Passkey Cross-Device Authentication

+ +
+

1. Origin-Info

+

Current Origin:

+

Is Secure Context:

+
+ +
+

2. OPTIONS Preflight Test

+ +

+  
+ +
+

3. POST Request Test (mit Origin-Header)

+ +

+  
+ +
+

4. Registration Options Test

+ +

+  
+ +
+

5. CORS Headers Check (Network Tab)

+

Öffne die Browser-Entwicklertools (F12) → Network Tab

+

Führe die Tests oben aus und prüfe:

+ +
+ + + + diff --git a/server/api/auth/register-passkey-options.post.js b/server/api/auth/register-passkey-options.post.js index f079183..d160f75 100644 --- a/server/api/auth/register-passkey-options.post.js +++ b/server/api/auth/register-passkey-options.post.js @@ -125,17 +125,28 @@ export default defineEventHandler(async (event) => { await writeAuditLog('auth.passkey.prereg.options', { email }) // CORS-Header für Cross-Device Authentication - if (requestOrigin) { - setHeader(event, 'Access-Control-Allow-Origin', requestOrigin) + // WICHTIG: Für Cross-Device muss CORS korrekt konfiguriert sein + const allowedOrigin = requestOrigin || webauthnOrigin + + if (allowedOrigin) { + setHeader(event, 'Access-Control-Allow-Origin', allowedOrigin) setHeader(event, 'Access-Control-Allow-Credentials', 'true') - setHeader(event, 'Access-Control-Allow-Methods', 'POST, OPTIONS') - setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization') - console.log('[DEBUG] CORS headers set', { origin: requestOrigin }) + setHeader(event, 'Access-Control-Allow-Methods', 'GET, POST, OPTIONS') + setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization, Origin, X-Requested-With') + setHeader(event, 'Access-Control-Max-Age', '86400') // 24 Stunden Cache für Preflight + console.log('[DEBUG] CORS headers set', { + origin: allowedOrigin, + requestOrigin, + webauthnOrigin, + method: getMethod(event) + }) } + // OPTIONS Preflight-Request für Cross-Device if (getMethod(event) === 'OPTIONS') { - console.log('[DEBUG] OPTIONS request, returning early') - return { success: true } + console.log('[DEBUG] OPTIONS preflight request, returning 204') + setResponseStatus(event, 204) + return null } // Stelle sicher, dass die Options korrekt serialisiert werden diff --git a/server/api/auth/register-passkey.post.js b/server/api/auth/register-passkey.post.js index cafbb65..c8e67a4 100644 --- a/server/api/auth/register-passkey.post.js +++ b/server/api/auth/register-passkey.post.js @@ -11,12 +11,30 @@ import { assertPasswordNotPwned } from '../../utils/hibp.js' export default defineEventHandler(async (event) => { const requestStart = Date.now() const requestOrigin = getHeader(event, 'origin') + const { origin: webauthnOrigin } = getWebAuthnConfig() console.log('[DEBUG] register-passkey request received', { origin: requestOrigin, + webauthnOrigin, timestamp: new Date().toISOString() }) + // CORS-Header für Cross-Device Authentication + const allowedOrigin = requestOrigin || webauthnOrigin + if (allowedOrigin) { + setHeader(event, 'Access-Control-Allow-Origin', allowedOrigin) + setHeader(event, 'Access-Control-Allow-Credentials', 'true') + setHeader(event, 'Access-Control-Allow-Methods', 'POST, OPTIONS') + setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization, Origin, X-Requested-With') + } + + // OPTIONS Preflight-Request + if (getMethod(event) === 'OPTIONS') { + console.log('[DEBUG] OPTIONS preflight request, returning 204') + setResponseStatus(event, 204) + return null + } + const body = await readBody(event) const registrationId = String(body?.registrationId || '') const response = body?.credential diff --git a/test-cors.html b/test-cors.html new file mode 100644 index 0000000..089188e --- /dev/null +++ b/test-cors.html @@ -0,0 +1,214 @@ + + + + CORS Test für Passkey Cross-Device + + + +

CORS Test für Passkey Cross-Device Authentication

+ +
+

1. Origin-Info

+

Current Origin:

+

Is Secure Context:

+
+ +
+

2. OPTIONS Preflight Test

+ +

+  
+ +
+

3. POST Request Test (mit Origin-Header)

+ +

+  
+ +
+

4. Registration Options Test

+ +

+  
+ +
+

5. CORS Headers Check (Network Tab)

+

Öffne die Browser-Entwicklertools (F12) → Network Tab

+

Führe die Tests oben aus und prüfe:

+ +
+ + + +