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) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-03-27 21:18:14 +01:00
parent 3180017f16
commit d95d142122
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
2 changed files with 8 additions and 3 deletions

View file

@ -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,

View file

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