{"record":{"id":"283b16f1fc38a263","repo":"passbolt/passbolt_api","slug":"ajax-json-request-not-supported-283b16","errorCode":null,"errorMessage":"Ajax/Json request not supported.","messagePattern":"Ajax/Json request not supported\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltEe/SsoRecover/src/Controller/OAuth2/OAuth2RecoverSuccessController.php","lineNumber":45,"sourceCode":"\nclass OAuth2RecoverSuccessController extends AbstractSsoController\n{\n    /**\n     * @inheritDoc\n     */\n    public function beforeFilter(EventInterface $event)\n    {\n        parent::beforeFilter($event);\n        $this->Authentication->allowUnauthenticated(['ssoRecoverSuccess']);\n    }\n\n    /**\n     * @return void\n     */\n    public function ssoRecoverSuccess(): void\n    {\n        if ($this->request->is('json')) {\n            throw new BadRequestException(__('Ajax/Json request not supported.'));\n        }\n\n        $this->User->assertNotLoggedIn();\n        $token = $this->getTokenFromUrlQuery();\n\n        try {\n            (new SsoAuthenticationTokenGetService())->getActiveNotExpiredOrFail($token, SsoState::TYPE_SSO_RECOVER);\n        } catch (RecordNotFoundException $e) {\n            throw new BadRequestException(\n                __('The authentication token does not exist or has been deleted.'),\n                null,\n                $e\n            );\n        } catch (CustomValidationException $e) {\n            throw new BadRequestException(\n                __('The authentication token has been expired.'),\n                null,\n                $e","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/SsoRecover/src/Controller/OAuth2/OAuth2RecoverSuccessController.php#L27-L63","documentation":"This BadRequestException is thrown by the OAuth2 SSO recover-success controller when the browser-only OAuth landing endpoint receives a JSON/Ajax request. Like its Azure/Google counterparts, this endpoint expects a full-page browser navigation following the OAuth2 provider redirect and rejects JSON requests up front.","triggerScenarios":"GET /sso/recover/success?token=... (OAuth2 variant) with Accept: application/json, X-Requested-With: XMLHttpRequest, or a JSON method-override header, e.g. when the URL is fetched by the passbolt JS API client or a script rather than loaded in the browser.","commonSituations":"curl/postman tests carrying JSON headers; a fetch/XHR call to the callback URL instead of following the OAuth redirect in the browser; browser extension or service worker rewriting the redirect.","solutions":["Use plain browser navigation to the success URL; remove JSON/Ajax headers from the request.","Do not call this endpoint programmatically; only follow the OAuth2 provider's redirect.","Automate with a headless browser or a plain HTTP client that follows redirects without JSON headers.","Inspect proxies/extensions that may convert the navigation into an Ajax request."],"exampleFix":"// before\nfetch('/sso/recover/success?token=...', { headers: { 'Accept': 'application/json' } });\n// after\nwindow.location.href = '/sso/recover/success?token=...';","handlingStrategy":"validation","validationCode":"const accept = headers.get('Accept') ?? '';\nif (accept.includes('application/json')) {\n  throw new Error('OAuth2 SSO success endpoint rejects JSON/Ajax requests; use browser navigation.');\n}","typeGuard":"function isPlainNavigation(headers) {\n  const h = new Headers(headers);\n  return !h.has('X-Requested-With') && !(h.get('Accept') ?? '').includes('application/json');\n}","tryCatchPattern":"try {\n  const res = await fetch(successUrl, { redirect: 'follow' });\n  if (res.status === 400 && (await res.text()).includes('Ajax/Json request not supported')) {\n    window.location.href = successUrl;\n  }\n} catch (e) { window.location.href = successUrl; }","preventionTips":["Treat /sso/recover/success (OAuth2) as a browser-only redirect landing page.","Do not fetch it with the passbolt JS API client's default JSON headers.","Automate OAuth flows with a redirect-following browser, not fetch/XHR.","Audit middleware/proxies that may add Ajax headers to the callback."],"tags":["http","sso","bad-request","ajax"],"backgroundTag":"unsupported-operation","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}