nextauthjs/next-auth · error · AuthError

Invalid WebAuthn Authentication response

Error message

Invalid WebAuthn Authentication response

What it means

verifyAuthenticate validates the WebAuthn assertion response shape before verification. It throws when the response data is missing, not an object, or lacks a string `id` property (the credential ID). The ID is required to normalize (base64url round-trip) and look up the authenticator.

Source

Thrown at packages/core/src/lib/utils/webauthn-utils.ts:221

export async function verifyAuthenticate(
  options: InternalOptionsWebAuthn,
  request: RequestInternal,
  resCookies: Cookie[]
): Promise<{ account: AdapterAccount; user: User }> {
  const { adapter, provider } = options

  // Get WebAuthn response from request body
  const data =
    request.body && typeof request.body.data === "string"
      ? (JSON.parse(request.body.data) as unknown)
      : undefined
  if (
    !data ||
    typeof data !== "object" ||
    !("id" in data) ||
    typeof data.id !== "string"
  ) {
    throw new AuthError("Invalid WebAuthn Authentication response")
  }

  // Reset the ID so we smooth out implementation differences
  const credentialID = toBase64(fromBase64(data.id))

  // Get authenticator from database
  const authenticator = await adapter.getAuthenticator(credentialID)
  if (!authenticator) {
    throw new AuthError(
      `WebAuthn authenticator not found in database: ${JSON.stringify({
        credentialID,
      })}`
    )
  }

  // Get challenge from request cookies
  const { challenge: expectedChallenge } = await webauthnChallenge.use(
    options,

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Ensure the client sends the full assertion including the string `id` field from navigator.credentials.get()
  2. Verify the server parses the request body as JSON before passing data to verifyAuthenticate
  3. Log the incoming data to confirm the shape and that `id` is present
  4. If using a custom client, map its credential field to `id` before sending

Example fix

// before
await verifyAuthenticate(request.body.text) // raw string, no id
// after
const data = JSON.parse(request.body)
if (data?.id) await verifyAuthenticate(data)
Defensive patterns

Strategy: type-guard

Validate before calling

const data = await request.json()
if (!data || typeof data !== 'object' || typeof data.id !== 'string') {
  return new Response('Invalid WebAuthn response', { status: 400 })
}

Type guard

function isValidAssertion(d: unknown): d is { id: string; [k: string]: unknown } {
  return !!d && typeof d === 'object' && 'id' in d && typeof (d as any).id === 'string'
}

Try / catch

try {
  await verifyAuthenticate(data)
} catch (e) {
  if (e instanceof AuthError && /Invalid WebAuthn Authentication response/.test(e.message)) {
    // return 400 and ask client to retry the ceremony
  }
}

Prevention

When it happens

Trigger: Calling verifyAuthenticate (via the `verified` action) with response data that is null, not an object, or whose `id` is not a string — e.g. a malformed client payload or a body-parsing step that dropped the id.

Common situations: Client sends an empty or truncated assertion; a custom client library returns id under a different key; request body parsed as text instead of JSON; browser API version differences changing field names.

Understand the failure class

Related errors


AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28). Data as JSON: /api/errors/13358ebdc834adab. Report an issue: GitHub.