mongodb/node-mongodb-native · error · MongoMissingCredentialsError

User provided OIDC callbacks must return a valid object…

Error message

User provided OIDC callbacks must return a valid object with an accessToken.

What it means

Thrown by the OIDC callback workflow when a user-supplied OIDC_CALLBACK or OIDC_HUMAN_CALLBACK returns a result that fails validation. The callback must return an object with a string accessToken; isCallbackResultInvalid() rejects results that are non-objects, lack accessToken, have a non-string accessToken, or carry disallowed properties. The cache is not populated on this failure.

Solutions

  1. Ensure the callback returns { accessToken: string, expiresInSeconds?: number, refreshToken?: string } with accessToken a non-empty string.
  2. Map your provider's fields: { accessToken: json.access_token, expiresInSeconds: Number(json.expires_in) }.
  3. Make sure the function actually returns (uses 'return' on the arrow/body) and that async fetch/parse errors are handled.

Example fix

// before
const cb = async () => {
  const r = await fetch(url);
  return (await r.json()); // returns { access_token, expires_in }
};
// after
const cb = async () => {
  const r = await fetch(url);
  const j = await r.json();
  return { accessToken: j.access_token, expiresInSeconds: Number(j.expires_in) };
};
Defensive patterns

Strategy: type-guard

Validate before calling

function isOidcResponse(v) {
  return !!v && typeof v === 'object'
    && typeof v.accessToken === 'string'
    && (v.expiresInSeconds == null || typeof v.expiresInSeconds === 'number')
    && (v.refreshToken == null || typeof v.refreshToken === 'string');
}
function safeCallback(inner) {
  return async (params) => {
    const r = await inner(params);
    if (!isOidcResponse(r)) throw new TypeError('OIDC callback returned invalid shape');
    return r;
  };
}

Type guard

function isOidcResponse(v): v is { accessToken: string; expiresInSeconds?: number; refreshToken?: string } {
  return !!v && typeof v === 'object'
    && typeof v.accessToken === 'string'
    && (v.expiresInSeconds == null || typeof v.expiresInSeconds === 'number')
    && (v.refreshToken == null || typeof v.refreshToken === 'string');
}

Prevention

When it happens

Trigger: The OIDC callback resolves with undefined, null, a string, or an object missing accessToken (or with accessToken not a string, or unexpected properties). Fires at callback_workflow.ts:147 inside executeAndValidateCallback().

Common situations: Callback returns the raw fetch Response or the parsed token JSON keyed differently (e.g. access_token instead of accessToken). Forgetting to return from an arrow function. Returning expiresInSeconds as a non-number. Including extra fields beyond accessToken/expiresInSeconds/refreshToken.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11). Data as JSON: /api/errors/c0bfef8a63962e41. Report an issue: GitHub.

Appendix: source

Thrown at src/cmap/auth/mongodb_oidc/callback_workflow.ts:147

    token: string,
    conversationId?: number
  ): Promise<void> {
    await connection.command(
      ns(credentials.source),
      finishCommandDocument(token, conversationId),
      undefined
    );
  }

  /**
   * Executes the callback and validates the output.
   */
  protected async executeAndValidateCallback(params: OIDCCallbackParams): Promise<OIDCResponse> {
    const result = await this.callback(params);
    // Validate that the result returned by the callback is acceptable. If it is not
    // we must clear the token result from the cache.
    if (isCallbackResultInvalid(result)) {
      throw new MongoMissingCredentialsError(CALLBACK_RESULT_ERROR);
    }
    return result;
  }

  /**
   * Ensure the callback is only executed one at a time and throttles the calls
   * to every 100ms.
   */
  protected withLock(callback: OIDCCallbackFunction): OIDCCallbackFunction {
    let lock: Promise<any> = Promise.resolve();
    return async (params: OIDCCallbackParams): Promise<OIDCResponse> => {
      // We do this to ensure that we would never return the result of the
      // previous lock, only the current callback's value would get returned.
      await lock;
      lock = lock

        .catch(() => null)

View on GitHub (pinned to dce7939f86)