Budibase/budibase · error · Error

Microsoft OAuth state is invalid or expired

Error message

Microsoft OAuth state is invalid or expired

What it means

After reading the state param, completeSharePointAuth looks it up in the cache and validates that a payload exists, its appId is a non-empty string, and its provider equals MICROSOFT_PROVIDER. Any mismatch or missing/expired cache entry throws this Error. Note the state cache entry is destroyed on read, so it is single-use.

Source

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

    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) {
    const description = String(ctx.query.error_description || "").trim()
    console.error("Microsoft OAuth authorization failed", {
      appId,
      error: oauthError,
      hasDescription: !!description,
    })
    throw new Error("Microsoft OAuth authorization failed")
  }

  const code = String(ctx.query.code || "").trim()
  if (!code) {
    throw new Error(
      "Microsoft OAuth callback is missing the authorization code"

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Restart the OAuth flow from the beginning to generate a fresh state.
  2. Check cache (Redis) availability, TTL for the state key, and that all server nodes share the same cache instance.
  3. Avoid replaying callback URLs; the state is destroyed after first successful validation.
  4. Confirm the provider stored at state-creation time matches the Microsoft provider constant used at callback.

Example fix

// before
// replaying an already-consumed callback URL (state destroyed on first read)
// after
await startSharePointAuth(appId) // generate fresh state, then complete flow once
Defensive patterns

Strategy: retry

Validate before calling

// no caller-side check possible; ensure flow is completed promptly and only once after start

Try / catch

try {
  await completeSharePointAuth(ctx)
} catch (e) {
  if (e.message === 'Microsoft OAuth state is invalid or expired') {
    // do not retry the same callback; restart the whole OAuth flow
    return restartSharePointAuth(appId)
  } else throw e
}

Prevention

When it happens

Trigger: Callback arrives more than the state TTL after flow start; the same callback URL is replayed a second time (state already consumed/destroyed); cache (Redis) restarted or flushed between start and callback; multi-node deployment where callback hits a node without the cached state; state was forged with an unknown provider value.

Common situations: Users double-clicking authorize links or refreshing the callback page; short-lived cache entries with slow manual auth; Redis eviction under memory pressure; mismatched provider constant after config changes.

Related errors


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