usebruno/bruno · error · Error

${JSON.stringify(tokenResponse)}

Error message

${JSON.stringify(tokenResponse)}

What it means

Thrown when the token endpoint returned an HTTP response containing an 'error' field — i.e. the authorization server rejected the token request (invalid_client, invalid_grant, invalid_scope, etc.). The helper serializes the entire response body as JSON so the caller sees the server's error object verbatim.

Solutions

  1. Parse the thrown message as JSON to read error and error_description — the exact server reason is there.
  2. For 'invalid_client': recheck clientId/clientSecret and credentialsPlacement (basic_auth_header vs body).
  3. For 'invalid_grant' with password flow: confirm username/password are correct and the account is active.
  4. For 'invalid_scope': trim the requested scopes to those registered for the client.

Example fix

// before — opaque error bubbling up
try { await getOAuth2AccessToken(config, tokenStore); }
catch (e) { console.log(e.message); }

// after — decode the server's error object
try { await getOAuth2AccessToken(config, tokenStore); }
catch (e) {
  try {
    const body = JSON.parse(e.message);
    console.error('OAuth2 error:', body.error, '-', body.error_description);
  } catch {
    console.error('Unexpected error:', e.message);
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Cannot fully prevent — server-side rejection. Validate inputs to reduce likelihood:
if (config.credentialsPlacement && !['basic_auth_header','body'].includes(config.credentialsPlacement)) {
  throw new Error('Bad credentialsPlacement');
}

Try / catch

try { await getOAuth2AccessToken(config, tokenStore); }
catch (e) {
  let body;
  try { body = JSON.parse(e.message); } catch { /* not a token-response error */ throw e; }
  if (body && body.error) {
    console.error('Token endpoint rejected:', body.error, body.error_description);
    // branch on body.error: invalid_client -> fix secret, invalid_grant -> re-auth, etc.
  } else throw e;
}

Prevention

When it happens

Trigger: Token request reached the server but the server responded with an OAuth2 error body, e.g. {error:'invalid_client', error_description:'...'} because of wrong client secret, bad credentials, or an unauthorized scope.

Common situations: Wrong client secret; expired/revoked refresh material; requested scope not permitted for the client; clock skew causing assertion failures; user account locked when using password grant.

Related errors


AI-assisted analysis of usebruno/bruno@9bdd81c7bd (2026-08-13). Data as JSON: /api/errors/ed625111a7714922. Report an issue: GitHub.

Appendix: source

Thrown at packages/bruno-requests/src/auth/oauth2-helper.ts:382

    if (!autoFetchToken) {
      // Don't fetch token if autoFetchToken is disabled
      return null;
    }
    // Otherwise, proceed to fetch new token
  }

  let tokenResponse;

  if (grantType === 'client_credentials') {
    tokenResponse = await fetchTokenClientCredentials(oauth2Config, axiosInstance);
  } else if (grantType === 'password') {
    tokenResponse = await fetchTokenPassword(oauth2Config, axiosInstance);
  } else {
    throw new Error(`Unsupported grant type: ${grantType}`);
  }

  if (tokenResponse.error) {
    throw new Error(JSON.stringify(tokenResponse));
  }

  if (!tokenResponse || !tokenResponse.access_token) {
    throw new Error('No access token received from server');
  }

  if (tokenResponse.expires_in && tokenResponse.created_at) {
    tokenResponse.expires_at = tokenResponse.created_at + tokenResponse.expires_in * 1000;
  }

  const saved = await tokenStore.saveCredential({ url: accessTokenUrl, credentialsId, credentials: tokenResponse });
  if (!saved) {
    console.warn('OAuth2: Failed to save token to store, but proceeding with token');
  }

  return tokenSource === 'id_token' ? tokenResponse.id_token : tokenResponse.access_token;
};

View on GitHub (pinned to 9bdd81c7bd)