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
- Confirm the credentialID exists by querying the authenticators table (or listUserAuthenticators) before updating the counter.
- Re-register the WebAuthn credential if it was deleted; then retry the counter update.
- Check credentialID encoding consistency (base64url vs base64 vs hex) between client, @simplewebauthn, and the stored column value.
- 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
- Normalize credentialID encoding (base64url string) everywhere before storing/comparing.
- Delete server-side authenticator records whenever a credential is removed client-side.
- Use one database per environment so registrations and counter updates land in the same store.
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.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28).
Data as JSON: /api/errors/ed4fc7a1a5a555d8.
Report an issue: GitHub.