Budibase/budibase · error · Error

Teams OAuth token request failed

Error message

Teams OAuth token request failed (${resp.status}): ${await resp.text()}

What it means

getOAuthToken performs an Azure AD client-credentials token request against login.microsoftonline.com using the Teams integration's appId/appPassword. If the HTTP response is not ok, it throws with the status and the raw response body so the Azure AD error (invalid_client, invalid scope, etc.) is visible. Called through the token getter used by both Bot Framework and Graph flows.

Solutions

  1. Verify the Teams integration's app ID, app password (client secret) and tenant ID match an active Azure AD app registration; regenerate the secret if it expired.
  2. Check the response body in the error message — Azure returns "invalid_client" for bad credentials or "invalid_scope" for permission issues; fix accordingly.
  3. Ensure the tenant ID is correct (use the directory/tenant GUID or "common") and that admin consent was granted for the required application permissions.
  4. Confirm outbound network access to login.microsoftonline.com from the server (proxy/firewall).

Example fix

// before
getOAuthToken(clientId, staleSecret, "", MS_SCOPE_BOT) // 400 invalid_client
// after
getOAuthToken(clientId, rotatedSecret, "00000000-0000-0000-0000-000000000000", MS_SCOPE_BOT)
Defensive patterns

Strategy: try-catch

Validate before calling

if (!clientId || !clientSecret || !tenantId) {
  throw new Error("Teams integration requires appId, appPassword and tenantId")
}

Try / catch

try {
  token = await getOAuthToken(clientId, secret, tenantId, scope)
} catch (err) {
  if (err.message.includes("invalid_client")) {
    // flag integration credentials as invalid, prompt reconfiguration
  } else if (err.message.includes("invalid_scope")) {
    // check admin-consented application permissions
  } else throw err
}

Prevention

When it happens

Trigger: The POST to https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token returns a non-2xx status — typically 400/401 from Azure AD.

Common situations: Wrong or missing msTenantId (e.g. empty because the agent's Teams integration has no tenant configured); wrong appPassword/client secret or a secret that expired or was rotated; app registration deleted; wrong scope requested or permissions not consented by admin; network/proxy blocking login.microsoftonline.com.

Related errors


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

Appendix: source

Thrown at packages/server/src/escalation/notifications/ms-teams.ts:75

): Promise<string> => {
  return cache.withCacheWithDynamicTTL(
    cache.CacheKey.OAUTH2_TOKEN(`teams_${msClientId}_${scope}`),
    async () => {
      const resp = await fetch(
        `https://login.microsoftonline.com/${msTenantId}/oauth2/v2.0/token`,
        {
          method: "POST",
          headers: { "Content-Type": "application/x-www-form-urlencoded" },
          body: new URLSearchParams({
            grant_type: "client_credentials",
            client_id: msClientId,
            client_secret: appPassword,
            scope,
          }).toString(),
        }
      )
      if (!resp.ok) {
        throw new Error(
          `Teams OAuth token request failed (${resp.status}): ${await resp.text()}`
        )
      }
      const data = (await resp.json()) as {
        access_token: string
        expires_in?: number
      }
      return { value: data.access_token, ttl: data.expires_in ?? 3600 }
    }
  )
}

const GRAPH_BASE = "https://graph.microsoft.com/v1.0"

const graphGet = async <T>(url: string, token: string): Promise<T> => {
  const resp = await fetch(url, {
    headers: { Authorization: `Bearer ${token}` },
  })

View on GitHub (pinned to a81a902e9a)