From 73ae8599c32c124208e4905b42e59b0a53285d2a Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 8 Jan 2026 16:11:30 +0100 Subject: [PATCH] Refactor Passkey Registration to utilize native WebAuthn API Update the test-smartphone.html to replace the use of @simplewebauthn/browser with the native WebAuthn API for Passkey registration. Enhance user feedback with additional messages regarding the use of the native API and improve the handling of user ID formats. Include detailed logging of credential creation options and ensure proper serialization of credential data for better debugging and compliance. --- public/test-smartphone.html | 86 ++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 21 deletions(-) diff --git a/public/test-smartphone.html b/public/test-smartphone.html index 426bda2..46e67f2 100644 --- a/public/test-smartphone.html +++ b/public/test-smartphone.html @@ -328,34 +328,77 @@ throw new Error('Invalid options response'); } - resultEl.textContent = 'Options erhalten. Starte WebAuthn startRegistration...\n\n'; + resultEl.textContent = 'Options erhalten. Starte WebAuthn Registrierung...\n\n'; resultEl.textContent += `Challenge: ${optionsData.options.challenge?.substring(0, 20)}...\n`; resultEl.textContent += `RP ID: ${optionsData.options.rp?.id}\n`; resultEl.textContent += `Timeout: ${optionsData.options.timeout}ms\n\n`; resultEl.textContent += 'Warte auf Passkey-Authentifizierung...\n'; - resultEl.textContent += '(Scanne QR-Code oder verwende lokalen Authenticator)\n'; + resultEl.textContent += '(Scanne QR-Code oder verwende lokalen Authenticator)\n\n'; + resultEl.textContent += 'HINWEIS: Diese Test-Seite verwendet die native WebAuthn-API.\n'; + resultEl.textContent += 'Die eigentliche App verwendet @simplewebauthn/browser (ist im Build enthalten).\n\n'; - // WebAuthn startRegistration - // Versuche, das Modul zu laden (kann fehlschlagen auf Smartphone) - let startRegistration; - try { - const mod = await import('https://unpkg.com/@simplewebauthn/browser@13.2.2/dist/bundle/index.umd.min.js'); - startRegistration = mod.startRegistration; - } catch (importError) { - // Fallback: Versuche, es lokal zu laden (wenn auf der Seite verfügbar) - resultEl.textContent += '\n\nWARNUNG: Konnte @simplewebauthn/browser nicht laden.\n'; - resultEl.textContent += 'Dies ist normal, wenn die Bibliothek nicht verfügbar ist.\n'; - resultEl.textContent += 'Die Options wurden aber erfolgreich vom Server erhalten.\n\n'; - resultEl.textContent += 'Options-Struktur:\n'; - resultEl.textContent += JSON.stringify(optionsData.options, null, 2); - resultEl.className = 'result warn'; - return; + // Verwende native WebAuthn-API (navigator.credentials.create) + // Dies ist die Basis-API, die @simplewebauthn/browser verwendet + const startTime = Date.now(); + + // Konvertiere user.id von Base64URL zu Uint8Array (falls nötig) + let userId; + if (typeof optionsData.options.user?.id === 'string') { + // Base64URL decode + const base64Url = optionsData.options.user.id; + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const binaryString = atob(base64); + userId = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + userId[i] = binaryString.charCodeAt(i); + } + } else if (optionsData.options.user?.id instanceof Uint8Array) { + userId = optionsData.options.user.id; + } else { + throw new Error('Invalid user.id format'); } - const startTime = Date.now(); - const credential = await startRegistration(optionsData.options); + // Erstelle PublicKeyCredentialCreationOptions + const publicKeyCredentialCreationOptions = { + challenge: Uint8Array.from(atob(optionsData.options.challenge.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)), + rp: optionsData.options.rp, + user: { + id: userId, + name: optionsData.options.user.name, + displayName: optionsData.options.user.displayName + }, + pubKeyCredParams: optionsData.options.pubKeyCredParams, + timeout: optionsData.options.timeout, + attestation: optionsData.options.attestation, + excludeCredentials: optionsData.options.excludeCredentials?.map(cred => ({ + id: Uint8Array.from(atob(cred.id.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)), + type: cred.type, + transports: cred.transports + })) || [], + authenticatorSelection: optionsData.options.authenticatorSelection, + extensions: optionsData.options.extensions + }; + + console.log('[DEBUG] Calling navigator.credentials.create with options:', publicKeyCredentialCreationOptions); + + const credential = await navigator.credentials.create({ + publicKey: publicKeyCredentialCreationOptions + }); + const duration = Date.now() - startTime; + // Konvertiere Credential zu einem serialisierbaren Format + const credentialData = { + id: credential.id, + rawId: Array.from(new Uint8Array(credential.rawId)), + type: credential.type, + response: { + clientDataJSON: Array.from(new Uint8Array(credential.response.clientDataJSON)), + attestationObject: Array.from(new Uint8Array(credential.response.attestationObject)) + }, + transports: credential.response.getTransports?.() || [] + }; + const result = { 'Status': 'SUCCESS ✓', 'Duration': `${duration}ms`, @@ -364,9 +407,10 @@ 'Type': credential.type, 'Raw ID': credential.rawId ? 'present' : 'missing', 'Response': credential.response ? 'present' : 'missing', - 'Transports': credential.transports || [] + 'Transports': credentialData.transports || [] }, - 'Full Credential': credential + 'Note': 'Dies ist die native WebAuthn-API. Die eigentliche App verwendet @simplewebauthn/browser.', + 'Credential Data': credentialData }; resultEl.className = 'result success';