toeverything/AFFiNE · error · InvalidOauthResponse
invalid_oauth_response
invalid_oauth_response
Error message
Invalid OAuth response: Unable to parse JSON response from ${url}. What it means
fetchJson got a 2xx response whose body is not valid JSON (empty bodies short-circuit to {}, so this is genuinely unparseable text). The provider URL answered successfully but with HTML or plain text — typically a login/error page, a rate-limit notice, or a proxy interstitial — so the OAuth client cannot deserialize it and throws InvalidOauthResponse naming the URL.
Source
Thrown at packages/backend/server/src/plugins/oauth/providers/def.ts:127
const body = await response.text();
if (!response.ok) {
if (response.status < 500 || options?.treatServerErrorAsInvalid) {
throw new InvalidOauthCallbackCode({ status: response.status, body });
}
throw new Error(
`Server responded with non-success status ${response.status}, body: ${body}`
);
}
if (!body) {
return {} as T;
}
try {
return JSON.parse(body) as T;
} catch {
throw new InvalidOauthResponse({
reason: `Unable to parse JSON response from ${url}`,
});
}
}
protected postFormJson<T>(
url: string,
body: string,
options?: {
headers?: Record<string, string>;
treatServerErrorAsInvalid?: boolean;
}
) {
return this.fetchJson<T>(
url,
{
method: 'POST',
body,View on GitHub (pinned to b6de0ad51b)
Solutions
- Log the URL in the error — fetch that exact URL with curl and inspect what comes back; if it's HTML, the endpoint is wrong or intercepted
- For OIDC, take endpoints from the issuer's /.well-known/openid-configuration rather than hand-assembling URLs
- Fix reverse-proxy routing so provider API paths are passed through, not rewritten to the frontend
Example fix
# before: OIDC token endpoint misconfigured OIDC_PROVIDER='https://sso.example.com' # discovery off, endpoints guessed wrong # after: let discovery resolve real endpoints, or configure exact JSON endpoints curl -s https://sso.example.com/.well-known/openid-configuration | jq .token_endpoint # -> https://sso.example.com/realms/affine/protocol/openid-connect/token
Defensive patterns
Strategy: try-catch
Validate before calling
const endpoints = await (await fetch(`${issuer}/.well-known/openid-configuration`)).json();
for (const key of ['token_endpoint', 'userinfo_endpoint', 'jwks_uri']) {
const head = await fetch(endpoints[key], { method: 'GET', headers: { Accept: 'application/json' } });
const ct = head.headers.get('content-type') ?? '';
if (!ct.includes('application/json')) throw new Error(`${key} (${endpoints[key]}) does not serve JSON — got ${ct}`);
} Type guard
function isInvalidOauthResponse(e: unknown): e is { code: 'invalid_oauth_response'; args: { reason: string } } {
return typeof e === 'object' && e !== null && (e as any).code === 'invalid_oauth_response';
} Try / catch
try {
await exchangeCode(code, state);
} catch (e) {
if (isInvalidOauthResponse(e) && e.args.reason.includes('Unable to parse JSON')) {
// endpoint returned HTML/plain text: log and fix the endpoint/proxy config, do not retry blindly
logger.error('OAuth endpoint returned non-JSON', { reason: e.args.reason });
return failLoginSetup();
}
throw e;
} Prevention
- Always derive endpoints from the IdP discovery document instead of hand-building URLs
- In reverse-proxy configs, ensure provider API routes pass through untouched — no HTML fallback for API paths
- Log the failing URL from the error reason; it names exactly which endpoint betrayed you
When it happens
Trigger: An OIDC endpoint URL misconfigured to an HTML page (using the issuer root instead of the token_endpoint, or discovery doc not actually OIDC); corporate proxy or captive portal injecting an HTML response; provider serving a maintenance/rate-limit page with status 200.
Common situations: OIDC_PROVIDER base URL with a typo; pointing token/userinfo endpoints at a path that returns the SPA index.html; reverse proxy (nginx) returning the frontend app for API paths; CDN rate-limiting with an HTML block page.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
AI-assisted analysis of toeverything/AFFiNE@b6de0ad51b (2026-08-18).
Data as JSON: /api/errors/a3a2cbeee84a9e6f.
Report an issue: GitHub.