Budibase/budibase · error

Error fetching oauth2 token: ${message}

Error message

Error fetching oauth2 token: ${message}

What it means

fetchAndParseToken POSTs client-credentials to the OAuth2 token endpoint. If the provider responds with a non-OK status, the thrown plain Error surfaces the provider's error_description (or HTTP statusText). This means the token request itself was rejected by the identity provider, not that a Budibase config was missing.

Source

Thrown at packages/server/src/sdk/workspace/oauth2/utils.ts:95

  await writethrough.patch({
    lastUsage: Date.now(),
  })
}

async function fetchAndParseToken(config: {
  url: string
  clientId: string
  clientSecret: string
  method: OAuth2CredentialsMethod
  grantType: OAuth2GrantType
  scope?: string
  audience?: string
}): Promise<{ value: string; ttl: number }> {
  const resp = await fetchToken(config)
  const jsonResponse = await resp.json()
  if (!resp.ok) {
    const message = jsonResponse.error_description ?? resp.statusText
    throw new Error(`Error fetching oauth2 token: ${message}`)
  }
  const token = `${jsonResponse.token_type} ${jsonResponse.access_token}`
  const ttl = jsonResponse.expires_in ?? -1
  return { value: token, ttl }
}

export async function getToken(id: string) {
  const token = await cache.withCacheWithDynamicTTL(
    cache.CacheKey.OAUTH2_TOKEN(id),
    async () => {
      const config = await get(id)
      if (!config) {
        throw new HTTPError(`oAuth config ${id} could not be found`, 400)
      }
      return fetchAndParseToken(config)
    }
  )

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Verify clientId/clientSecret and grant type against the provider's settings
  2. Check the token URL is the exact token endpoint (e.g. https://provider/oauth/token)
  3. Ensure required scope/audience values are configured
  4. Read the error_description in the message — it names the provider's exact rejection reason
  5. Confirm env-var placeholders used in the config resolve to non-empty values

Example fix

// before
// config.clientSecret stored as {{env.SECRET}} but env var missing
// after
// set the env var (or hardcode a valid secret in dev) and redeploy so
// processEnvironmentVariable resolves a real secret before the token POST
Defensive patterns

Strategy: try-catch

Validate before calling

if (!config.url || !config.clientId || !config.clientSecret) {
  throw new Error('OAuth2 config incomplete: url, clientId and clientSecret are required')
}

Type guard

null

Try / catch

try {
  const token = await getToken(configId)
} catch (e) {
  if (/^Error fetching oauth2 token:/.test(String(e.message))) {
    // provider rejected the request: log e.message (contains error_description),
    // fix credentials/URL/scope, and apply backoff before retrying
  } else throw e
}

Prevention

When it happens

Trigger: Calling getToken(id) (or getTokenFromConfig) where fetchToken receives a 4xx/5xx response: wrong client_id/client_secret, unsupported grant_type, invalid scope/audience, token URL pointing at a non-token endpoint, or the endpoint returning an error body that is not JSON-encodable errors.

Common situations: Expired or rotated client secrets after provider-side changes; Auth0-style providers requiring an audience parameter that was not set; scope names changed by the provider; environment-variable substitution producing an empty secret; firewalls/proxies returning HTML error pages that break resp.json().

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/dffca3e0e6d7c6cf. Report an issue: GitHub.