nextauthjs/next-auth · error

Authenticator not found.

Error message

Authenticator not found.

What it means

SQLite twin of the pg adapter guard: updateAuthenticatorCounter updates the WebAuthn counter by credentialID and returns the row via RETURNING. When no authenticator matches the credentialID, it throws 'Authenticator not found.'

Source

Thrown at packages/adapter-drizzle/src/lib/sqlite.ts:330

        .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 DefaultSQLiteColumn<
  T extends {
    data: string | boolean | number | Date
    dataType: "string" | "boolean" | "number" | "date"
    notNull: boolean
    isPrimaryKey?: boolean
    columnType:
      | "SQLiteText"
      | "SQLiteBoolean"
      | "SQLiteTimestamp"
      | "SQLiteInteger"
  },

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Verify the credential exists (query authenticatorsTable by credentialID) before updating.
  2. Re-register the passkey/credential against this database.
  3. Ensure credentialID column type (text vs blob) matches the value format passed in — normalize to a consistent encoding (e.g. base64url string).
  4. Confirm you are pointed at the same SQLite database used during registration.

Example fix

// before
await adapter.updateAuthenticatorCounter(rawCredId, counter)
// after
const credId = toBase64UrlString(rawCredId)
const existing = await db.select().from(authenticatorsTable)
  .where(eq(authenticatorsTable.credentialID, credId))
if (!existing.length) throw new Error(`credential ${credId} not registered`)
await adapter.updateAuthenticatorCounter(credId, counter)
Defensive patterns

Strategy: validation

Validate before calling

const rows = await db.select().from(authenticatorsTable)
  .where(eq(authenticatorsTable.credentialID, credentialID))
if (rows.length === 0) 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(`Credential ${credentialID} unknown; require re-enrollment`)
    return
  }
  throw e
}

Prevention

When it happens

Trigger: updateAuthenticatorCounter(credentialID, newCounter) where the credentialID is absent from the SQLite authenticators table — credential never registered, deleted, or stored with different encoding.

Common situations: Deleting the SQLite file (or reseeding) while browsers retain registered passkeys; credentialID stored as BLOB but compared as string (or vice versa) so the eq() match fails; switching between pg and sqlite adapters where credentials were only registered in one.

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/30db54f502c0c0e6. Report an issue: GitHub.