Enhance debug logging and Cross-Device support for Passkey Registration
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 45s
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 45s
Update the registrieren.vue component to include detailed debug statements for the Cross-Device authentication flow, specifically during QR-Code generation. Improve logging in the register-passkey-options and register-passkey APIs to capture request details such as user agent and IP address, aiding in troubleshooting. Additionally, introduce a new function to retrieve pre-registration data, enhancing the overall registration process and compliance with Cross-Device requirements.
This commit is contained in:
@@ -630,6 +630,18 @@ const handleRegisterWithPasskey = async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Neue API-Struktur: { optionsJSON: options }
|
// Neue API-Struktur: { optionsJSON: options }
|
||||||
|
// WICHTIG: Bei Cross-Device wird der QR-Code automatisch generiert
|
||||||
|
// Das Smartphone scannt den QR-Code und öffnet die Website
|
||||||
|
// Dann ruft das Smartphone startRegistration auf, um die Credential zu erstellen
|
||||||
|
// Die Credential-Response wird dann an den Desktop-Browser zurückgesendet
|
||||||
|
console.log('[DEBUG] About to call startRegistration - this will trigger QR code generation for Cross-Device')
|
||||||
|
console.log('[DEBUG] If using Cross-Device:')
|
||||||
|
console.log('[DEBUG] 1. QR-Code wird angezeigt')
|
||||||
|
console.log('[DEBUG] 2. Smartphone scannt QR-Code')
|
||||||
|
console.log('[DEBUG] 3. Smartphone öffnet:', window.location.origin)
|
||||||
|
console.log('[DEBUG] 4. Smartphone muss startRegistration aufrufen können')
|
||||||
|
console.log('[DEBUG] 5. Smartphone sendet Credential-Response an:', window.location.origin + '/api/auth/register-passkey')
|
||||||
|
|
||||||
credential = await mod.startRegistration({ optionsJSON: pre.options })
|
credential = await mod.startRegistration({ optionsJSON: pre.options })
|
||||||
|
|
||||||
clearTimeout(timeoutWarning)
|
clearTimeout(timeoutWarning)
|
||||||
@@ -640,7 +652,8 @@ const handleRegisterWithPasskey = async () => {
|
|||||||
credentialId: credential?.id,
|
credentialId: credential?.id,
|
||||||
responseType: credential?.response?.constructor?.name,
|
responseType: credential?.response?.constructor?.name,
|
||||||
transports: credential?.transports,
|
transports: credential?.transports,
|
||||||
durationSeconds: Math.round(webauthnDuration / 1000)
|
durationSeconds: Math.round(webauthnDuration / 1000),
|
||||||
|
note: 'Wenn Cross-Device verwendet wurde, sollte die Credential-Response vom Smartphone kommen'
|
||||||
})
|
})
|
||||||
} catch (webauthnError) {
|
} catch (webauthnError) {
|
||||||
const webauthnDuration = Date.now() - webauthnStart
|
const webauthnDuration = Date.now() - webauthnStart
|
||||||
|
|||||||
@@ -4,10 +4,17 @@ export default defineEventHandler(async (event) => {
|
|||||||
const requestOrigin = getHeader(event, 'origin')
|
const requestOrigin = getHeader(event, 'origin')
|
||||||
const { origin: webauthnOrigin } = getWebAuthnConfig()
|
const { origin: webauthnOrigin } = getWebAuthnConfig()
|
||||||
|
|
||||||
console.log('[DEBUG] OPTIONS preflight request received', {
|
const userAgent = getHeader(event, 'user-agent')
|
||||||
origin: requestOrigin,
|
const ip = getHeader(event, 'x-forwarded-for') || getHeader(event, 'x-real-ip') || 'unknown'
|
||||||
|
|
||||||
|
console.log('[DEBUG] ===== OPTIONS preflight for /api/auth/register-passkey-options =====')
|
||||||
|
console.log('[DEBUG] OPTIONS Request Details:', {
|
||||||
|
origin: requestOrigin || 'none',
|
||||||
webauthnOrigin,
|
webauthnOrigin,
|
||||||
timestamp: new Date().toISOString()
|
userAgent: userAgent?.substring(0, 100) || 'none',
|
||||||
|
ip,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
note: 'OPTIONS Preflight für Passkey Registration Options'
|
||||||
})
|
})
|
||||||
|
|
||||||
// CORS-Header für Cross-Device Authentication
|
// CORS-Header für Cross-Device Authentication
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { getPreRegistration } from '../../../utils/webauthn-challenges.js'
|
||||||
|
import { generateRegistrationOptions } from '@simplewebauthn/server'
|
||||||
|
import { getWebAuthnConfig } from '../../../utils/webauthn-config.js'
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const registrationId = getRouterParam(event, 'registrationId')
|
||||||
|
const requestOrigin = getHeader(event, 'origin')
|
||||||
|
const userAgent = getHeader(event, 'user-agent')
|
||||||
|
|
||||||
|
console.log('[DEBUG] ===== GET register-passkey-options/[registrationId] =====')
|
||||||
|
console.log('[DEBUG] Request Details:', {
|
||||||
|
registrationId,
|
||||||
|
origin: requestOrigin,
|
||||||
|
userAgent: userAgent?.substring(0, 100),
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
method: getMethod(event),
|
||||||
|
note: 'Dieser Endpoint wird vom Smartphone aufgerufen, um die Options für Cross-Device zu erhalten'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!registrationId) {
|
||||||
|
throw createError({ statusCode: 400, message: 'registrationId ist erforderlich' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hole Pre-Registration-Daten
|
||||||
|
const pre = getPreRegistration(registrationId)
|
||||||
|
if (!pre) {
|
||||||
|
console.error('[DEBUG] Pre-registration not found', { registrationId })
|
||||||
|
throw createError({ statusCode: 404, message: 'Registrierungs-Session nicht gefunden oder abgelaufen' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const { challenge, userId, name, email } = pre
|
||||||
|
console.log('[DEBUG] Pre-registration found', {
|
||||||
|
userId,
|
||||||
|
email: email?.substring(0, 10) + '...',
|
||||||
|
hasChallenge: !!challenge
|
||||||
|
})
|
||||||
|
|
||||||
|
const { rpId, rpName, origin: webauthnOrigin } = getWebAuthnConfig()
|
||||||
|
|
||||||
|
// Generiere Options neu (mit der gespeicherten Challenge)
|
||||||
|
const options = await generateRegistrationOptions({
|
||||||
|
rpName,
|
||||||
|
rpID: rpId,
|
||||||
|
userID: new TextEncoder().encode(String(userId)),
|
||||||
|
userName: email,
|
||||||
|
userDisplayName: name,
|
||||||
|
attestationType: 'none',
|
||||||
|
authenticatorSelection: {
|
||||||
|
residentKey: 'preferred',
|
||||||
|
userVerification: 'preferred'
|
||||||
|
},
|
||||||
|
timeout: 300000,
|
||||||
|
challenge: challenge // Verwende die gespeicherte Challenge
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('[DEBUG] Options regenerated for Cross-Device', {
|
||||||
|
hasChallenge: !!options.challenge,
|
||||||
|
challengeMatches: options.challenge === challenge,
|
||||||
|
rpId: options.rp?.id
|
||||||
|
})
|
||||||
|
|
||||||
|
// CORS-Header für Cross-Device
|
||||||
|
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', 'GET, OPTIONS')
|
||||||
|
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization, Origin, X-Requested-With')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
registrationId,
|
||||||
|
options
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -4,10 +4,17 @@ export default defineEventHandler(async (event) => {
|
|||||||
const requestOrigin = getHeader(event, 'origin')
|
const requestOrigin = getHeader(event, 'origin')
|
||||||
const { origin: webauthnOrigin } = getWebAuthnConfig()
|
const { origin: webauthnOrigin } = getWebAuthnConfig()
|
||||||
|
|
||||||
console.log('[DEBUG] OPTIONS preflight request received (register-passkey)', {
|
const userAgent = getHeader(event, 'user-agent')
|
||||||
origin: requestOrigin,
|
const ip = getHeader(event, 'x-forwarded-for') || getHeader(event, 'x-real-ip') || 'unknown'
|
||||||
|
|
||||||
|
console.log('[DEBUG] ===== OPTIONS preflight for /api/auth/register-passkey =====')
|
||||||
|
console.log('[DEBUG] OPTIONS Request Details:', {
|
||||||
|
origin: requestOrigin || 'none',
|
||||||
webauthnOrigin,
|
webauthnOrigin,
|
||||||
timestamp: new Date().toISOString()
|
userAgent: userAgent?.substring(0, 100) || 'none',
|
||||||
|
ip,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
note: 'OPTIONS Preflight für Cross-Device Passkey - Wenn dieser Request vom Smartphone kommt, sollte der User-Agent Mobile/Android/iPhone enthalten'
|
||||||
})
|
})
|
||||||
|
|
||||||
// CORS-Header für Cross-Device Authentication
|
// CORS-Header für Cross-Device Authentication
|
||||||
|
|||||||
@@ -15,16 +15,27 @@ export default defineEventHandler(async (event) => {
|
|||||||
const userAgent = getHeader(event, 'user-agent')
|
const userAgent = getHeader(event, 'user-agent')
|
||||||
const { origin: webauthnOrigin } = getWebAuthnConfig()
|
const { origin: webauthnOrigin } = getWebAuthnConfig()
|
||||||
|
|
||||||
console.log('[DEBUG] ===== register-passkey request received =====')
|
console.log('')
|
||||||
|
console.log('='.repeat(80))
|
||||||
|
console.log('[DEBUG] 🔑 ===== PASSKEY REGISTRATION REQUEST RECEIVED =====')
|
||||||
|
console.log('='.repeat(80))
|
||||||
console.log('[DEBUG] Request Details:', {
|
console.log('[DEBUG] Request Details:', {
|
||||||
origin: requestOrigin,
|
origin: requestOrigin || 'none',
|
||||||
webauthnOrigin,
|
webauthnOrigin,
|
||||||
userAgent: userAgent?.substring(0, 150),
|
userAgent: userAgent?.substring(0, 150) || 'none',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
method: getMethod(event),
|
method: getMethod(event),
|
||||||
ip: getClientIp(event),
|
ip: getClientIp(event),
|
||||||
note: 'Dieser Request sollte vom Smartphone kommen, wenn der QR-Code gescannt wurde'
|
url: getRequestURL(event).href,
|
||||||
|
path: getRequestURL(event).pathname
|
||||||
})
|
})
|
||||||
|
console.log('[DEBUG] ⚠️ WICHTIG: Dieser Request sollte vom Smartphone kommen, wenn der QR-Code gescannt wurde')
|
||||||
|
console.log('[DEBUG] ⚠️ Wenn dieser Request NICHT im Log erscheint, bedeutet das:')
|
||||||
|
console.log('[DEBUG] 1. Das Smartphone konnte die Website nicht erreichen')
|
||||||
|
console.log('[DEBUG] 2. Oder startRegistration auf dem Smartphone ist fehlgeschlagen')
|
||||||
|
console.log('[DEBUG] 3. Oder die Credential-Response wurde nicht gesendet')
|
||||||
|
console.log('='.repeat(80))
|
||||||
|
console.log('')
|
||||||
console.log('[DEBUG] User-Agent Analysis:', {
|
console.log('[DEBUG] User-Agent Analysis:', {
|
||||||
isMobile: /Mobile|Android|iPhone|iPad/i.test(userAgent || ''),
|
isMobile: /Mobile|Android|iPhone|iPad/i.test(userAgent || ''),
|
||||||
isChrome: /Chrome/i.test(userAgent || ''),
|
isChrome: /Chrome/i.test(userAgent || ''),
|
||||||
|
|||||||
41
server/middleware/log-requests.js
Normal file
41
server/middleware/log-requests.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Globales Request-Logging für Debugging
|
||||||
|
* Loggt alle Requests, besonders Passkey-relevante Endpoints
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default defineEventHandler((event) => {
|
||||||
|
const url = getRequestURL(event)
|
||||||
|
const method = getMethod(event)
|
||||||
|
const path = url.pathname
|
||||||
|
const origin = getHeader(event, 'origin')
|
||||||
|
const userAgent = getHeader(event, 'user-agent')
|
||||||
|
const ip = getHeader(event, 'x-forwarded-for') || getHeader(event, 'x-real-ip') || 'unknown'
|
||||||
|
|
||||||
|
// Logge nur Passkey-relevante Endpoints (um Logs nicht zu überfluten)
|
||||||
|
const passkeyEndpoints = [
|
||||||
|
'/api/auth/register-passkey',
|
||||||
|
'/api/auth/register-passkey-options',
|
||||||
|
'/api/auth/passkeys/registration-options',
|
||||||
|
'/api/auth/passkeys/register',
|
||||||
|
'/api/auth/passkeys/authentication-options',
|
||||||
|
'/api/auth/passkeys/login',
|
||||||
|
'/api/auth/passkeys/recovery'
|
||||||
|
]
|
||||||
|
|
||||||
|
const isPasskeyEndpoint = passkeyEndpoints.some(ep => path.startsWith(ep))
|
||||||
|
|
||||||
|
if (isPasskeyEndpoint) {
|
||||||
|
const timestamp = new Date().toISOString()
|
||||||
|
console.log(`[REQUEST] ${timestamp} ${method} ${path}`)
|
||||||
|
console.log(`[REQUEST] Origin: ${origin || 'none'}`)
|
||||||
|
console.log(`[REQUEST] IP: ${ip}`)
|
||||||
|
console.log(`[REQUEST] User-Agent: ${userAgent?.substring(0, 100) || 'none'}`)
|
||||||
|
|
||||||
|
// Spezielle Logs für Cross-Device
|
||||||
|
if (path.includes('register-passkey')) {
|
||||||
|
console.log(`[REQUEST] 🔑 PASSKEY REQUEST - ${method} ${path}`)
|
||||||
|
console.log(`[REQUEST] ⚠️ Wenn dieser Request vom Smartphone kommt, sollte der User-Agent Mobile/Android/iPhone enthalten`)
|
||||||
|
console.log(`[REQUEST] ⚠️ Wenn dieser Request NICHT kommt, erreicht das Smartphone den Server nicht`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -59,4 +59,12 @@ export function consumePreRegistration(registrationId) {
|
|||||||
return v.payload || null
|
return v.payload || null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPreRegistration(registrationId) {
|
||||||
|
cleanup(preRegChallenges)
|
||||||
|
const key = String(registrationId)
|
||||||
|
const v = preRegChallenges.get(key)
|
||||||
|
if (!v) return null
|
||||||
|
return v.payload || null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user