Enhance debug logging and Cross-Device support for Passkey Registration
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:
Torsten Schulz (local)
2026-01-08 23:27:11 +01:00
parent badf91afef
commit 29ef644581
7 changed files with 174 additions and 11 deletions

View File

@@ -630,6 +630,18 @@ const handleRegisterWithPasskey = async () => {
})
// 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 })
clearTimeout(timeoutWarning)
@@ -640,7 +652,8 @@ const handleRegisterWithPasskey = async () => {
credentialId: credential?.id,
responseType: credential?.response?.constructor?.name,
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) {
const webauthnDuration = Date.now() - webauthnStart

View File

@@ -4,10 +4,17 @@ export default defineEventHandler(async (event) => {
const requestOrigin = getHeader(event, 'origin')
const { origin: webauthnOrigin } = getWebAuthnConfig()
console.log('[DEBUG] OPTIONS preflight request received', {
origin: requestOrigin,
const userAgent = getHeader(event, 'user-agent')
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,
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

View File

@@ -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
}
})

View File

@@ -4,10 +4,17 @@ export default defineEventHandler(async (event) => {
const requestOrigin = getHeader(event, 'origin')
const { origin: webauthnOrigin } = getWebAuthnConfig()
console.log('[DEBUG] OPTIONS preflight request received (register-passkey)', {
origin: requestOrigin,
const userAgent = getHeader(event, 'user-agent')
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,
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

View File

@@ -15,16 +15,27 @@ export default defineEventHandler(async (event) => {
const userAgent = getHeader(event, 'user-agent')
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:', {
origin: requestOrigin,
origin: requestOrigin || 'none',
webauthnOrigin,
userAgent: userAgent?.substring(0, 150),
userAgent: userAgent?.substring(0, 150) || 'none',
timestamp: new Date().toISOString(),
method: getMethod(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:', {
isMobile: /Mobile|Android|iPhone|iPad/i.test(userAgent || ''),
isChrome: /Chrome/i.test(userAgent || ''),

View 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`)
}
}
})

View File

@@ -59,4 +59,12 @@ export function consumePreRegistration(registrationId) {
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
}