{"record":{"id":"a85d5b94f33419a4","repo":"passbolt/passbolt_api","slug":"ajax-json-request-not-supported-a85d5b","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/Azure/AzureRecoverSuccessController.php","lineNumber":47,"sourceCode":"{\n    /**\n     * @inheritDoc\n     */\n    public function beforeFilter(EventInterface $event)\n    {\n        parent::beforeFilter($event);\n\n        $this->Authentication->allowUnauthenticated(['ssoRecoverSuccess']);\n    }\n\n    /**\n     * @return void\n     * @throws \\League\\OAuth2\\Client\\Provider\\Exception\\IdentityProviderException\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":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/SsoRecover/src/Controller/Azure/AzureRecoverSuccessController.php#L29-L65","documentation":"This BadRequestException is thrown by the Azure SSO recover-success controller when a client sends the request with a JSON/Ajax Accept or Content-Type header. The SSO recover success endpoint is a browser redirect target (full-page HTML flow) and is deliberately not part of the JSON API, so any JSON-formatted request is rejected immediately before any token validation.","triggerScenarios":"A client calls GET /sso/recover/success/azure (the OAuth2 redirect landing URL) with the 'X-Http-Method-Override' or Accept header indicating JSON (e.g. the passbolt JS API client default headers, Accept: application/json), or an Ajax/fetch call is made against this browser-only endpoint.","commonSituations":"Developers testing the SSO recover flow via curl/postman with passbolt's typical JSON headers, or a browser extension/script intercepting the OAuth redirect and re-issuing it as an Ajax request instead of a full navigation.","solutions":["Remove JSON-related headers (Accept: application/json, X-Requested-With, X-Http-Method-Override) and request the endpoint as a plain browser navigation/redirect.","Do not invoke the recover-success URL from API code; only follow the OAuth2 provider redirect in a browser.","If automating the flow, use a headless browser or follow redirects with a plain HTTP client that does not set JSON headers.","If you landed here from the JS API, switch to the regular (non-JSON) fetch or window.location navigation."],"exampleFix":"// before\nawait fetch('/sso/recover/success/azure?token=...', { headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' } });\n// after\nwindow.location.href = '/sso/recover/success/azure?token=...';","handlingStrategy":"validation","validationCode":"const url = new URL(successUrl, window.location.origin);\nconst isJsonRequest = headers.has('Accept') && headers.get('Accept').includes('application/json');\nif (isJsonRequest) throw new Error('Use browser navigation, not a JSON request, for the SSO success endpoint.');","typeGuard":"function isAjaxHeader(headers) {\n  return headers instanceof Headers &&\n    (headers.get('X-Requested-With') === 'XMLHttpRequest' ||\n     (headers.get('Accept') ?? '').includes('application/json'));\n}","tryCatchPattern":"try {\n  const res = await fetch('/sso/recover/success/azure?token=' + token, { redirect: 'follow' });\n  if (res.status === 400 && (await res.text()).includes('Ajax/Json request not supported')) {\n    window.location.href = '/sso/recover/success/azure?token=' + token;\n  }\n} catch (e) { /* network errors */ }","preventionTips":["Treat all /sso/recover/success/* endpoints as browser-only redirect targets.","Strip passbolt JSON API default headers when hitting browser-flow endpoints.","Never proxy the OAuth redirect through an Ajax/fetch call.","Document that these endpoints return HTML, not JSON."],"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"}