nextauthjs/next-auth · error

Authenticator not found.

Error message

Authenticator not found.

What it means

updateAuthenticatorCounter updates a WebAuthn authenticator's counter by credentialID and returns the updated row. When the UPDATE ... RETURNING finds no authenticator with that credentialID, the adapter throws 'Authenticator not found.' because there is nothing to update.

Source

Thrown at packages/adapter-drizzle/src/lib/pg.ts:322

        .where(eq(authenticatorsTable.credentialID, credentialID))
        .then((res) => res[0] ?? null) as Awaitable<AdapterAuthenticator | null>
    },
    async listAuthenticatorsByUserId(userId: string) {
      return client
        .select()
        .from(authenticatorsTable)
        .where(eq(authenticatorsTable.userId, userId))
        .then((res) => res) as Awaitable<AdapterAuthenticator[]>
    },
    async updateAuthenticatorCounter(credentialID: string, newCounter: number) {
      const authenticator = await client
        .update(authenticatorsTable)
        .set({ counter: newCounter })
        .where(eq(authenticatorsTable.credentialID, credentialID))
        .returning()
        .then((res) => res[0])

      if (!authenticator) throw new Error("Authenticator not found.")

      return authenticator as Awaitable<AdapterAuthenticator>
    },
  }
}

type DefaultPostgresColumn<
  T extends {
    data: string | number | boolean | Date
    dataType: "string" | "number" | "boolean" | "date"
    notNull: boolean
    isPrimaryKey?: boolean
    columnType:
      | "PgVarchar"
      | "PgText"
      | "PgBoolean"
      | "PgTimestamp"
      | "PgInteger"

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Confirm the credentialID exists by querying the authenticators table (or listUserAuthenticators) before updating the counter.
  2. Re-register the WebAuthn credential if it was deleted; then retry the counter update.
  3. Check credentialID encoding consistency (base64url vs base64 vs hex) between client, @simplewebauthn, and the stored column value.
  4. Verify the adapter is connected to the same database used during registration.

Example fix

// before
await adapter.updateAuthenticatorCounter(credId, counter)
// after
const auth = await adapter.listAuthenticatorsByUserId(userId)
if (!auth.some(a => a.credentialID === credId)) {
  throw new Error(`Unknown credentialID ${credId}`)
}
await adapter.updateAuthenticatorCounter(credId, counter)
Defensive patterns

Strategy: validation

Validate before calling

const known = await adapter.listAuthenticatorsByUserId(userId)
if (!known.some(a => a.credentialID === credentialID)) {
  throw new Error(`credentialID ${credentialID} not registered`)
}
await adapter.updateAuthenticatorCounter(credentialID, counter)

Try / catch

try {
  await adapter.updateAuthenticatorCounter(credentialID, counter)
} catch (e) {
  if ((e as Error).message === 'Authenticator not found.') {
    console.warn(`Unknown credential ${credentialID}; forcing re-registration`)
    return
  }
  throw e
}

Prevention

When it happens

Trigger: Calling updateAuthenticatorCounter(credentialID, newCounter) with a credentialID that is not stored in the authenticators table — e.g. the credential was deleted, registered in a different database, or the ID string differs in encoding.

Common situations: WebAuthn flows where the authenticator was removed server-side but the browser still presents the credential; running against a fresh database without prior authenticator registration; base64url vs hex encoding mismatches of credentialID between client and DB.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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