{"record":{"id":"264dd441f588e205","repo":"BookStackApp/BookStack","slug":"invalid-sls-response","errorCode":null,"errorMessage":"Invalid SLS Response: ","messagePattern":"Invalid SLS Response: ","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/Access/Saml2Service.php","lineNumber":141,"sourceCode":"     * Process a response for the single logout service.\n     *\n     * @throws Error\n     */\n    public function processSlsResponse(?string $requestId): string\n    {\n        $toolkit = $this->getToolkit();\n\n        // The $retrieveParametersFromServer in the call below will mean the library will take the query\n        // parameters, used for the response signing, from the raw $_SERVER['QUERY_STRING']\n        // value so that the exact encoding format is matched when checking the signature.\n        // This is primarily due to ADFS encoding query params with lowercase percent encoding while\n        // PHP (And most other sensible providers) standardise on uppercase.\n        /** @var ?string $samlRedirect */\n        $samlRedirect = $toolkit->processSLO(true, $requestId, true, null, true);\n        $errors = $toolkit->getErrors();\n\n        if (!empty($errors)) {\n            throw new Error(\n                'Invalid SLS Response: ' . implode(', ', $errors)\n            );\n        }\n\n        $defaultBookStackRedirect = $this->loginService->logout();\n\n        return $samlRedirect ?? $defaultBookStackRedirect;\n    }\n\n    /**\n     * Get the metadata for this service provider.\n     *\n     * @throws Error\n     */\n    public function metadata(): string\n    {\n        $toolKit = $this->getToolkit(true);\n        $settings = $toolKit->getSettings();","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Access/Saml2Service.php#L123-L159","documentation":"Thrown by Saml2Service::processSlsResponse when the OneLogin toolkit's processSLO() (Single Logout) records errors via getErrors() after processing the logout response/request from the IdP. It signals the SLO message failed validation — commonly a bad signature on the redirect-encoded logout message or a malformed SLO payload. The exception is raised before BookStack performs its local logout via loginService->logout().","triggerScenarios":"The IdP redirects back to the SLS endpoint with query params (SAMLResponse/SAMLRequest + Signature); processSLO(true, $requestId, true, null, true) reads params from the raw $_SERVER['QUERY_STRING'] to verify the signature. Errors arise when the SLS response signature doesn't validate (wrong/rotated IdP cert, proxy or web server re-encoding the query string, e.g. lowercase percent-encoding like ADFS), the relaystate/request-id is mismatched, or the SLO message is malformed.","commonSituations":"ADFS behind IIS/URL-rewrite lowercasing percent-encoded query strings so signature checks fail (the code comments on exactly this); IdP certificate updated but SP still has the old one; reverse proxy normalizing query params; IdP-initiated logout with an unexpected session index; clock skew invalidating SLO timestamps.","solutions":["Verify the IdP x509 certificate configured in BookStack matches the cert used to sign the logout message (re-import current IdP metadata).","Check for proxies/rewrite rules altering the query string encoding before it reaches the SLS endpoint; bypass or normalize so the raw QUERY_STRING signature checks out.","Confirm the SLS endpoint URL registered at the IdP matches BookStack's saml2 SLS route exactly.","Inspect the toolkit error strings in the message and enable SAML debug logging for the exact validation failure; compare with IdP SLO logs.","If SLO keeps failing in your environment, accept that local sessions may need manual cleanup: users can be logged out server-side by admin, and you can disable IdP-initiated SLO if the IdP supports it."],"exampleFix":"// before (nginx normalizing query string)\nproxy_pass http://app;\n\n// after (preserve raw request URI/args for SLS signature check)\nproxy_pass http://app;\nproxy_set_header Request_URI $request_uri;","handlingStrategy":"try-catch","validationCode":"// Preconditions before hitting the SLS endpoint:\n// 1. IdP signing cert in SP settings is current\n// 2. No proxy/rewrite layer mutates the raw query string (compare $_SERVER['QUERY_STRING'] with what the IdP sent)\n// 3. SLS URL registered at the IdP matches the saml2 SLS route exactly","typeGuard":null,"tryCatchPattern":"use OneLogin\\Saml2\\Error as Saml2Error;\n\ntry {\n    $redirect = $saml2Service->processSlsResponse($requestId);\n} catch (Saml2Error $e) {\n    report($e);\n    // Fail safe: still terminate the local session even if IdP SLO validation failed\n    auth()->logout();\n    return redirect('/');\n}","preventionTips":["Verify proxies/reverse-proxies pass the raw, unmodified QUERY_STRING (percent-encoding preserved)","Update the IdP certificate in SP settings promptly after any IdP cert rotation","Test SAML logout (not just login) in staging after every IdP or proxy configuration change","Log the toolkit error strings to pinpoint signature vs timestamp vs payload issues"],"tags":["php","saml","sso","logout","signature-validation"],"backgroundTag":"saml-logout-signature-invalid","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}