nextauthjs/next-auth · error

Authenticator not found.

Error message

Authenticator not found.

What it means

updateAuthenticatorCounter looks up an authenticator (WebAuthn credential) by credentialID and throws this error when no row is returned. It cannot update the counter for a credential that is not registered in the authenticators table.

Source

Thrown at packages/adapter-drizzle/src/lib/mysql.ts:374

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

      const authenticator = await client
        .select()
        .from(authenticatorsTable)
        .where(eq(authenticatorsTable.credentialID, credentialID))
        .then((res) => res[0])

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

      return authenticator as Awaitable<AdapterAuthenticator>
    },
  }
}

type DefaultMyqlColumn<
  T extends {
    data: string | number | boolean | Date
    dataType: "string" | "number" | "boolean" | "date"
    notNull: boolean
    isPrimaryKey?: boolean
    columnType:
      | "MySqlVarChar"
      | "MySqlText"
      | "MySqlBoolean"
      | "MySqlTimestamp"
      | "MySqlInt"

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Ensure createAuthenticator was called during registration and the row committed before authentication.
  2. Check the authenticators table contains the credentialID (SELECT manually).
  3. Verify both registration and authentication flows use the same database/connection.
  4. If the credential was deleted server-side, remove it client-side or re-register the passkey.

Example fix

// before
await adapter.updateAuthenticatorCounter(credentialID)
// after
const authenticator = await adapter.getAuthenticator(credentialID)
if (!authenticator) throw new Error(`Credential ${credentialID} not registered`)
await adapter.updateAuthenticatorCounter(credentialID)
Defensive patterns

Strategy: validation

Validate before calling

const authenticator = await adapter.getAuthenticator(credentialID)
if (!authenticator) throw new Error(`Authenticator ${credentialID} is not registered`)

Type guard

function isRegisteredAuthenticator(a: AdapterAuthenticator | null): a is AdapterAuthenticator {
  return a !== null && typeof a.credentialID === 'string'
}

Try / catch

try {
  await adapter.updateAuthenticatorCounter(credentialID)
} catch (e) {
  if (e instanceof Error && e.message === 'Authenticator not found.') {
    // treat as re-registration required; do not retry blindly
  }
  throw e
}

Prevention

When it happens

Trigger: adapter.updateAuthenticatorCounter(credentialID) with a credentialID that has no matching row — the authenticator was never registered via createAuthenticator, was deleted, or the credentialID doesn't match what was stored.

Common situations: WebAuthn login flow where the credential exists on the device but was never persisted (createAuthenticator skipped), wiping the database while keeping browser credentials, or multiple databases (register in one, authenticate against another).

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/8391e9e27dee4b8e. Report an issue: GitHub.