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
- Ensure createAuthenticator was called during registration and the row committed before authentication.
- Check the authenticators table contains the credentialID (SELECT manually).
- Verify both registration and authentication flows use the same database/connection.
- 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
- Always call createAuthenticator during WebAuthn registration before allowing logins
- Keep registration and authentication on the same database
- Purge client-side passkeys when server-side credentials are deleted
- Verify credentialID values match exactly what was stored (no trimming/encoding drift)
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.
- 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/8391e9e27dee4b8e.
Report an issue: GitHub.