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

  1. Pass the exact credentialID string stored in the authenticator document
  2. Check base64url encode/decode of credentialID between browser and server is symmetric
  3. Guard the call site: only invoke when the authenticator record was actually fetched
  4. 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

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


AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28). Data as JSON: /api/errors/032396f48f754a27. Report an issue: GitHub.