Budibase/budibase · error · Error
Microsoft OAuth response did not include a refresh token
Error message
Microsoft OAuth response did not include a refresh token
What it means
completeSharePointAuth exchanges an OAuth authorization code with Microsoft's token endpoint. Microsoft requires the offline_access scope to return a refresh_token; this error means the token response parsed fine but had no refresh_token field, so SharePoint credentials cannot be stored for long-term use.
Source
Thrown at packages/server/src/api/controllers/ai/sharepointAuth.ts:164
redirect_uri: callbackUrl,
scope: DEFAULT_SCOPE,
}),
})
const tokenPayload = await tokenResponse.json()
if (!tokenResponse.ok) {
console.error("Microsoft OAuth token exchange failed", {
appId,
status: tokenResponse.status,
error: tokenPayload?.error,
hasDescription: !!tokenPayload?.error_description,
})
throw new Error("Failed to exchange Microsoft OAuth code")
}
const refreshToken = tokenPayload?.refresh_token
const accessToken = tokenPayload?.access_token
if (!refreshToken) {
throw new Error("Microsoft OAuth response did not include a refresh token")
}
if (!accessToken) {
throw new Error("Microsoft OAuth response did not include an access token")
}
const expiresIn = Number(tokenPayload?.expires_in || 0)
const tokenType = tokenPayload?.token_type || "Bearer"
const bearerToken = `${tokenType} ${accessToken}`
let account = "unknown"
try {
const meResponse = await fetch(
`${MICROSOFT_GRAPH_BASE}/me?$select=displayName,mail,userPrincipalName`,
{
headers: {
Authorization: bearerToken,
},
}View on GitHub (pinned to a81a902e9a)
Solutions
- Add offline_access to the OAuth authorization scope and have the user re-consent
- Verify the Azure AD app has the required delegated permissions (offline_access, Files.Read.All/Sites.Read.All) and admin consent granted
- Log tokenPayload keys (never values) to confirm which scopes were actually granted, then retry the code exchange
Example fix
// before
const authUrl = `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize?...&scope=Files.Read.All`
// after
const authUrl = `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize?...&scope=offline_access Files.Read.All` Defensive patterns
Strategy: validation
Validate before calling
const scopes = new URLSearchParams(authUrl).get('scope') || ''
if (!scopes.split(' ').includes('offline_access')) throw new Error('offline_access scope required for refresh token') Type guard
function hasRefreshToken(p: unknown): p is { refresh_token: string } {
return typeof p === 'object' && p !== null && 'refresh_token' in p && typeof (p as { refresh_token: unknown }).refresh_token === 'string'
} Try / catch
try {
await completeSharePointAuth(params)
} catch (e) {
if (e.message.includes('refresh token')) {
// restart OAuth flow with offline_access scope
}
} Prevention
- Always include offline_access in Microsoft OAuth scopes
- Grant admin consent for all required delegated permissions in Azure AD
- Capture and log granted scopes (not values) after token exchange to detect drift early
When it happens
Trigger: Calling completeSharePointAuth with a valid code but the OAuth authorize URL omitted the offline_access scope (or the tenant admin revoked it), so Microsoft's token response lacks refresh_token.
Common situations: Constructing a custom consent URL without offline_access; admin consent flows that drop scopes; Microsoft sometimes omits refresh_token on first-party/implicit flows or when the app is configured for token-only auth.
Related errors
- Microsoft OAuth response did not include an access token
- No Microsoft datasource configuration found
- Microsoft OAuth callback is missing state
- Microsoft OAuth state is invalid or expired
- Microsoft OAuth authorization failed
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/b11267fc9b3fbdcc.
Report an issue: GitHub.