{"record":{"id":"ccadb3a0f36ed434","repo":"Budibase/budibase","slug":"microsoft-oauth-callback-is-missing-state","errorCode":null,"errorMessage":"Microsoft OAuth callback is missing state","messagePattern":"Microsoft OAuth callback is missing state","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/server/src/api/controllers/ai/sharepointAuth.ts","lineNumber":96,"sourceCode":"  authorizeUrl.searchParams.set(\"response_type\", \"code\")\n  authorizeUrl.searchParams.set(\"redirect_uri\", callbackUrl)\n  authorizeUrl.searchParams.set(\"response_mode\", \"query\")\n  authorizeUrl.searchParams.set(\"scope\", DEFAULT_SCOPE)\n  authorizeUrl.searchParams.set(\"prompt\", \"select_account\")\n  authorizeUrl.searchParams.set(\"state\", state)\n\n  ctx.redirect(authorizeUrl.toString())\n}\n\nexport async function completeSharePointAuth(ctx: UserCtx<void, void>) {\n  const authStateCookie = utils.getCookie<DatasourceAuthCookie>(\n    ctx,\n    constants.Cookie.DatasourceAuth\n  )\n\n  const state = String(ctx.query.state || \"\").trim()\n  if (!state) {\n    throw new Error(\"Microsoft OAuth callback is missing state\")\n  }\n  const statePayload = (await cache.get(\n    `datasource:${MICROSOFT_PROVIDER}:state:${state}`\n  )) as { appId?: string; provider?: string }\n  await cache.destroy(`datasource:${MICROSOFT_PROVIDER}:state:${state}`)\n  const stateAppId =\n    typeof statePayload?.appId === \"string\" ? statePayload.appId.trim() : \"\"\n  if (\n    !statePayload ||\n    !stateAppId ||\n    statePayload.provider !== MICROSOFT_PROVIDER\n  ) {\n    throw new Error(\"Microsoft OAuth state is invalid or expired\")\n  }\n  const appId = stateAppId\n\n  const oauthError = String(ctx.query.error || \"\").trim()\n  if (oauthError) {","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/api/controllers/ai/sharepointAuth.ts#L78-L114","documentation":"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.","triggerScenarios":"Microsoft redirects to the callback URL without ?state=..., or the client hits the callback endpoint directly/manually with a stripped-down URL.","commonSituations":"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.","solutions":["Ensure the redirect_uri registered in Azure AD exactly matches the one used to start the flow, so Microsoft echoes state back.","Check any reverse proxy config preserves query strings on the callback route.","Always initiate OAuth via the start endpoint so a state value is generated and stored in cache."],"exampleFix":"// before\ncallbackUrl = `${base}/api/ai/sharepoint/callback` // query stripped by redirect\n// after\ncallbackUrl = `${base}/api/ai/sharepoint/callback?state=${encodeURIComponent(state)}`","handlingStrategy":"try-catch","validationCode":"const url = new URL(callbackUrl)\nif (!url.searchParams.get('state')) throw new Error('callback URL must include state query param')","typeGuard":null,"tryCatchPattern":"try {\n  await completeSharePointAuth(ctx)\n} catch (e) {\n  if (e.message === 'Microsoft OAuth callback is missing state') {\n    // redirect user to restart the OAuth flow\n  } else throw e\n}","preventionTips":["Always start OAuth via the official start endpoint so state is generated.","Verify proxies/reverse-proxies preserve query strings on the callback route.","Test the full browser redirect flow, not just the callback endpoint in isolation."],"tags":["oauth","csrf","microsoft","sharepoint","callback"],"backgroundTag":"oauth-state-missing-or-invalid","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}