From d95d14212210211f03cac5d32570955c5dd3f473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Fri, 27 Mar 2026 21:18:14 +0100 Subject: [PATCH] Fix passkey credential ID encoding and improve error handling Bug: @simplewebauthn v13 changed credential.id from Uint8Array to base64url string. Buffer.from(string) without encoding stored the ASCII text instead of decoded bytes. Authentication then failed to match because it correctly decoded with "base64url". Fix: Buffer.from(credential.id, "base64url") in both registration and add-passkey flows. Also: catch WebAuthn "not allowed" errors and show a friendly message instead of the raw browser error. Existing passkeys must be re-registered after deploy. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/journal/app/lib/auth.server.ts | 4 ++-- apps/journal/app/routes/auth.login.tsx | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/journal/app/lib/auth.server.ts b/apps/journal/app/lib/auth.server.ts index 8905332..a85a55e 100644 --- a/apps/journal/app/lib/auth.server.ts +++ b/apps/journal/app/lib/auth.server.ts @@ -81,7 +81,7 @@ export async function finishRegistration( await db.insert(credentials).values({ id: randomUUID(), userId, - credentialId: Buffer.from(credential.id), + credentialId: Buffer.from(credential.id, "base64url"), publicKey: Buffer.from(credential.publicKey), counter: credential.counter, transports: response.response.transports, @@ -137,7 +137,7 @@ export async function addPasskeyFinish( await db.insert(credentials).values({ id: randomUUID(), userId, - credentialId: Buffer.from(credential.id), + credentialId: Buffer.from(credential.id, "base64url"), publicKey: Buffer.from(credential.publicKey), counter: credential.counter, transports: response.response.transports, diff --git a/apps/journal/app/routes/auth.login.tsx b/apps/journal/app/routes/auth.login.tsx index 9698f19..eaa0ed1 100644 --- a/apps/journal/app/routes/auth.login.tsx +++ b/apps/journal/app/routes/auth.login.tsx @@ -47,7 +47,12 @@ export default function LoginPage() { window.location.href = "/"; } } catch (err) { - setError((err as Error).message); + const message = (err as Error).message; + if (message.includes("timed out") || message.includes("not allowed")) { + setError("No passkey found for this site. Register a new account or use a magic link instead."); + } else { + setError(message); + } } finally { setLoading(false); }