{"record":{"id":"c30703dacb99dca6","repo":"toeverything/AFFiNE","slug":"invalid-oauth-response-c30703","errorCode":"invalid_oauth_response","errorMessage":"Invalid OAuth response: Missing PKCE challenge for OIDC authorization request.","messagePattern":"Invalid OAuth response: Missing PKCE challenge for OIDC authorization request\\.","errorType":"exception","errorClass":"InvalidOauthResponse","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/plugins/oauth/providers/oidc.ts","lineNumber":252,"sourceCode":"      `OIDC discovery validation failed, retrying in ${delay}ms`\n    );\n  }\n\n  private resetState() {\n    this.#endpoints = null;\n    this.#jwks = null;\n  }\n\n  getAuthUrl(state: string): string {\n    const parsedState = this.parseStatePayload(state);\n    const nonce = parsedState?.state ?? state;\n    const pkce = parsedState?.pkce;\n\n    if (\n      this.requiresPkce &&\n      (!pkce?.codeChallenge || !pkce.codeChallengeMethod)\n    ) {\n      throw new InvalidOauthResponse({\n        reason: 'Missing PKCE challenge for OIDC authorization request',\n      });\n    }\n\n    const query: JWTPayload = {\n      client_id: this.config.clientId,\n      redirect_uri: this.url.link('/oauth/callback'),\n      scope: this.resolveScope(this.config.args?.scope),\n      response_type: 'code',\n      ...omit(\n        this.config.args,\n        'claim_id',\n        'claim_email',\n        'claim_name',\n        'claim_email_verified'\n      ),\n      state,\n      nonce,","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/oauth/providers/oidc.ts#L234-L270","documentation":"OIDCProvider.getAuthUrl builds the authorization URL from the state blob. When the provider requires PKCE but the state payload carries no pkce.codeChallenge/codeChallengeMethod (or the state string isn't a parseable state envelope, so parsedState is null), it refuses to construct a PKCE-less authorization request and throws InvalidOauthResponse. PKCE material is minted server-side in /oauth/preflight and embedded in state — its absence means the state didn't come from a current preflight.","triggerScenarios":"Calling getAuthUrl with a raw/legacy state string: a state from a login started before the server upgrade that added PKCE, a state string hand-crafted by a third-party client, or a stale cached authorize URL being re-used after the IdP turned on PKCE enforcement.","commonSituations":"Server upgraded mid-flight — users with an open login tab carry old-format state; custom clients building their own authorize URLs instead of going through preflight; browser back-button resubmitting an old authorize URL.","solutions":["Always initiate login via POST /oauth/preflight and use the state/authorize URL it returns — never construct state yourself","Invalidate cached/pending authorize URLs after upgrading the server; users mid-flow should restart login","If the IdP mandates PKCE (e.g. config requires it), ensure the server version that created the state already supports PKCE"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Only ever feed getAuthUrl the state string produced by POST /oauth/preflight\nconst { state, authorize_url } = await post('/oauth/preflight', {\n  provider: 'OIDC', client: 'web', client_nonce: crypto.randomUUID(),\n});\n// verify it is the envelope format this server version issues\nconst parsed = JSON.parse(state);\nif (!parsed.state || !parsed.pkce?.codeChallenge) {\n  throw new Error('Preflight returned a state without PKCE — server/provider PKCE config mismatch');\n}\nwindow.location.href = authorize_url ?? provider.getAuthUrl(state);","typeGuard":"function isPkceStateEnvelope(s: string): boolean {\n  try {\n    const p = JSON.parse(s);\n    return typeof p.state === 'string' && typeof p.pkce?.codeChallenge === 'string';\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  window.location.href = provider.getAuthUrl(state);\n} catch (e) {\n  if ((e as any).code === 'invalid_oauth_response' && /PKCE/i.test((e as any).args?.reason ?? '')) {\n    return restartFromPreflight(); // state predates PKCE — mint a fresh one\n  }\n  throw e;\n}","preventionTips":["Never construct authorize URLs or state yourself; always obtain them from the current server's preflight","After server upgrades, treat pre-existing login tabs/URLs as expired and force users to restart","Reject cached authorize URLs on the client (bookmark/back-button) before redirecting"],"tags":["oauth","oidc","pkce","state","authorization-url"],"backgroundTag":"oauth-pkce-missing","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-08-23T06:17:17.905Z"}