langgenius/dify · error · BaseError
access_denied
access_denied
Error message
authorization denied
What it means
Raised by DatasourceOAuthCallback.get (GET /oauth/plugin/{provider_id}/datasource/callback) when neither the context_id cookie nor the context_id query parameter is present. The callback is browser-redirected from the OAuth provider, which normally carries the context_id cookie set by the get-authorization-url endpoint. Its absence means the cookie was never set, was cleared, or the callback was invoked directly. Maps to HTTP 403 via werkzeug Forbidden.
Source
Thrown at cli/src/commands/auth/login/device-flow.ts:54
device_code: code.device_code,
client_id: opts.clientId ?? DEFAULT_CLIENT_ID,
}
while (true) {
if (opts.clock.isCancelled()) throw expired()
const result = await pollWithRetry(api, req, opts.clock)
switch (result.status) {
case 'approved':
return result.success
case 'pending':
break
case 'slow_down':
interval = Math.min(interval * 2, MAX_INTERVAL_MS)
break
case 'expired':
throw expired()
case 'denied':
throw new BaseError({
code: ErrorCode.AccessDenied,
message: 'authorization denied',
})
case 'retry_5xx':
throw new BaseError({
code: ErrorCode.Server5xx,
message: 'device-flow poll unavailable after retries',
})
}
await opts.clock.sleepMs(interval)
if (opts.clock.isCancelled()) throw expired()
}
}
async function pollWithRetry(
api: DeviceFlowApiSubset,
req: PollRequest,
clock: Clock,View on GitHub (pinned to ef8544b173)
Solutions
- Always initiate the flow via GET /oauth/plugin/{provider_id}/datasource/get-authorization-url so the context_id cookie is set first.
- Ensure the browser accepts first-party cookies for the console domain (check SameSite=Lax cookie behavior).
- If the cookie expired, restart the OAuth flow from the authorization-url endpoint.
- For programmatic testing, pass context_id as a query parameter matching one obtained from create_proxy_context.
Example fix
// before — opening the callback URL directly
window.location = `/console/api/oauth/plugin/${providerId}/datasource/callback?code=${code}`;
// after — start from the authorization-url endpoint so the cookie is set
window.location = `/console/api/oauth/plugin/${providerId}/datasource/get-authorization-url`; Defensive patterns
Strategy: validation
Validate before calling
function hasContextId(): boolean {
return Boolean(getCookie('context_id') || new URLSearchParams(location.search).get('context_id'));
}
if (!hasContextId()) {
// start the OAuth flow from the top
window.location = `/console/api/oauth/plugin/${providerId}/datasource/get-authorization-url`;
} Type guard
function isOAuthInitiated(contextId: string | null | undefined): contextId is string {
return typeof contextId === 'string' && contextId.length > 0;
} Try / catch
try {
await fetch(callbackUrl);
} catch (e) {
if (e.response?.status === 403 && /context_id not found/i.test(e.response.data?.message || '')) {
// restart the flow to set the cookie
window.location = `/console/api/oauth/plugin/${providerId}/datasource/get-authorization-url`;
return;
}
throw e;
} Prevention
- Always begin OAuth from the get-authorization-url endpoint so the context_id cookie is set.
- Ensure the browser accepts first-party cookies for the console domain.
- Never deep-link the callback URL; it requires the cookie from the initiation step.
When it happens
Trigger: OAuth provider redirects back to /callback but the user's browser dropped the context_id cookie (third-party cookie blocking, different domain, incognito); a developer manually opens the callback URL in a new tab without first hitting get-authorization-url; the cookie expired (max_age = OAuthProxyService.__MAX_AGE__).
Common situations: Browser blocks third-party/same-site cookies because the OAuth provider redirects across origins; Safari ITP stripping the cookie; user copied the callback URL instead of following the redirect flow; long delay between authorization and callback exceeding cookie max_age.
Related errors
AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12).
Data as JSON: /api/errors/66fcd48570c607e9.
Report an issue: GitHub.