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.

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)

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.