nextauthjs/next-auth · error
credential id is required
Error message
credential id is required
What it means
updateAuthenticatorCounter() bumps a WebAuthn credential's counter after successful authentication. It validates upfront that credentialId is truthy and throws 'credential id is required' otherwise, preventing a SurrealDB UPDATE with no WHERE target.
Source
Thrown at packages/adapter-surrealdb/src/index.ts:539
const surreal = await client
try {
const [authenticatorDocs] = await surreal.query<[AuthenticatorDoc[]]>(
`SELECT * FROM authenticator WHERE userId = $userId LIMIT 1`,
{
userId,
}
)
return authenticatorDocs.map((v) => docToAuthenticator(v))
} catch {}
throw new Error("Verification Token not found")
},
async updateAuthenticatorCounter(
credentialId: AdapterAuthenticator["credentialID"],
newCounter: AdapterAuthenticator["counter"]
) {
try {
if (!credentialId) throw new Error("credential id is required")
const surreal = await client
const [authenticatorDoc] = await surreal.query<[AuthenticatorDoc]>(
`UPDATE ONLY authenticator MERGE $doc WHERE credentialID = $cid`,
{
cid: credentialId,
doc: {
counter: newCounter,
},
}
)
return docToAuthenticator(authenticatorDoc)
} catch {}
throw Error(
`Unable to update authenticator with credential ${credentialId}`
)
},
}
}View on GitHub (pinned to a1a16a5a77)
Solutions
- Pass the exact credentialID string stored in the authenticator document
- Check base64url encode/decode of credentialID between browser and server is symmetric
- Guard the call site: only invoke when the authenticator record was actually fetched
- Log credentialId before the call to confirm it is non-empty
Example fix
// before
await adapter.updateAuthenticatorCounter(credential.id, credential.counter)
// after
if (!credential?.id) throw new Error('Missing credentialID from assertion')
await adapter.updateAuthenticatorCounter(credential.id, credential.counter) Defensive patterns
Strategy: validation
Validate before calling
if (typeof credentialId !== 'string' || credentialId.length === 0) {
throw new Error('Cannot update counter: credentialId missing')
}
await adapter.updateAuthenticatorCounter(credentialId, newCounter) Type guard
function hasCredentialId(id: unknown): id is string {
return typeof id === 'string' && id.length > 0
} Try / catch
try {
await adapter.updateAuthenticatorCounter(credentialId, counter)
} catch (e) {
if (e.message === 'credential id is required') {
console.error('Assertion credentialID lost during decode — check base64url handling')
}
throw e
} Prevention
- Use symmetric base64url encode/decode for credentialID between browser and server
- Never pass request-body fields to the adapter without validating presence
- Preserve credentialID when deserializing WebAuthn assertion results
When it happens
Trigger: Calling updateAuthenticatorCounter(undefined/null/'', counter); credentialID lost when deserializing the WebAuthn credential (e.g. base64url decoding produced undefined); custom code driving the adapter directly.
Common situations: Passkey authentication where the credentialID from the browser assertion wasn't converted back to the stored string form; building the call from a request body missing the credential id field.
Related errors
- User id is required
- Authenticator not created
- Verification Token not found
- Invalid action parameter
- Error creating or finding account
AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28).
Data as JSON: /api/errors/032396f48f754a27.
Report an issue: GitHub.