Budibase/budibase · error · Error

Microsoft OAuth callback is missing state

Error message

Microsoft OAuth callback is missing state

What it means

completeSharePointAuth reads `state` from the OAuth callback query string and throws if it is empty. The state parameter is required to correlate the callback with the pending auth request stored in cache and to protect against CSRF.

Source

Thrown at packages/server/src/api/controllers/ai/sharepointAuth.ts:96

  authorizeUrl.searchParams.set("response_type", "code")
  authorizeUrl.searchParams.set("redirect_uri", callbackUrl)
  authorizeUrl.searchParams.set("response_mode", "query")
  authorizeUrl.searchParams.set("scope", DEFAULT_SCOPE)
  authorizeUrl.searchParams.set("prompt", "select_account")
  authorizeUrl.searchParams.set("state", state)

  ctx.redirect(authorizeUrl.toString())
}

export async function completeSharePointAuth(ctx: UserCtx<void, void>) {
  const authStateCookie = utils.getCookie<DatasourceAuthCookie>(
    ctx,
    constants.Cookie.DatasourceAuth
  )

  const state = String(ctx.query.state || "").trim()
  if (!state) {
    throw new Error("Microsoft OAuth callback is missing state")
  }
  const statePayload = (await cache.get(
    `datasource:${MICROSOFT_PROVIDER}:state:${state}`
  )) as { appId?: string; provider?: string }
  await cache.destroy(`datasource:${MICROSOFT_PROVIDER}:state:${state}`)
  const stateAppId =
    typeof statePayload?.appId === "string" ? statePayload.appId.trim() : ""
  if (
    !statePayload ||
    !stateAppId ||
    statePayload.provider !== MICROSOFT_PROVIDER
  ) {
    throw new Error("Microsoft OAuth state is invalid or expired")
  }
  const appId = stateAppId

  const oauthError = String(ctx.query.error || "").trim()
  if (oauthError) {

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Ensure the redirect_uri registered in Azure AD exactly matches the one used to start the flow, so Microsoft echoes state back.
  2. Check any reverse proxy config preserves query strings on the callback route.
  3. Always initiate OAuth via the start endpoint so a state value is generated and stored in cache.

Example fix

// before
callbackUrl = `${base}/api/ai/sharepoint/callback` // query stripped by redirect
// after
callbackUrl = `${base}/api/ai/sharepoint/callback?state=${encodeURIComponent(state)}`
Defensive patterns

Strategy: try-catch

Validate before calling

const url = new URL(callbackUrl)
if (!url.searchParams.get('state')) throw new Error('callback URL must include state query param')

Try / catch

try {
  await completeSharePointAuth(ctx)
} catch (e) {
  if (e.message === 'Microsoft OAuth callback is missing state') {
    // redirect user to restart the OAuth flow
  } else throw e
}

Prevention

When it happens

Trigger: Microsoft redirects to the callback URL without ?state=..., or the client hits the callback endpoint directly/manually with a stripped-down URL.

Common situations: Redirect URI misconfigured so query params are dropped; a proxy or reverse proxy rewrites the callback URL and strips the query string; user bookmarked a partial callback URL; manual testing of the callback endpoint.

Related errors


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