{"record":{"id":"dde1719d27b61bd4","repo":"BookStackApp/BookStack","slug":"invalid-acs-response-errors-implode-erro","errorCode":null,"errorMessage":"Invalid ACS Response; Errors: {implode(', ', $errors)}; Reason: {$reason}","messagePattern":"Invalid ACS Response; Errors: (.+?); Reason: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/Access/Saml2Service.php","lineNumber":108,"sourceCode":"     * @throws SamlException\n     * @throws ValidationError\n     * @throws JsonDebugException\n     * @throws UserRegistrationException\n     */\n    public function processAcsResponse(?string $requestId, string $samlResponse): ?User\n    {\n        // The SAML2 toolkit expects the response to be within the $_POST superglobal\n        // so we need to manually put it back there at this point.\n        $_POST['SAMLResponse'] = $samlResponse;\n        $toolkit = $this->getToolkit();\n        $toolkit->processResponse($requestId);\n        $errors = $toolkit->getErrors();\n\n        if (!empty($errors)) {\n            $reason = $toolkit->getLastErrorReason();\n            $message = 'Invalid ACS Response; Errors: ' . implode(', ', $errors);\n            $message .= $reason ? \"; Reason: {$reason}\" : '';\n            throw new Error($message);\n        }\n\n        if (!$toolkit->isAuthenticated()) {\n            return null;\n        }\n\n        $attrs = $toolkit->getAttributes();\n        $id = $toolkit->getNameId();\n        session()->put('saml2_session_index', $toolkit->getSessionIndex());\n\n        return $this->processLoginCallback($id, $attrs);\n    }\n\n    /**\n     * Process a response for the single logout service.\n     *\n     * @throws Error\n     */","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Access/Saml2Service.php#L90-L126","documentation":"Thrown by Saml2Service::processAcsResponse when the OneLogin php-saml toolkit's processResponse() records errors via getErrors() after consuming the SAML Response POSTed to the Assertion Consumer Service (ACS). The message aggregates all toolkit error strings plus getLastErrorReason() for detail. It means the IdP's SAML response could not be accepted/validated — the SP refuses to proceed with login, before isAuthenticated() is even checked.","triggerScenarios":"processAcsResponse() is called on the ACS callback with a POSTed SAMLResponse; toolkit->processResponse($requestId) fails with validation errors such as: response signature invalid (wrong IdP x509 cert configured), clock skew ('SAML Response too old' / NotOnOrAfter in the past due to server time drift), invalid audience/destination (SP entity_id or ACS URL mismatch), missing requested NameID format, or the request-id doesn't match the pending auth request.","commonSituations":"IdP certificate rotated (or test/prod IdP mixed up) so signature validation fails; server clocks out of sync causing 'response is too old'; SAML settings (entityId, ACS URL, idp.entityId/idp.sso URL) mismatched between BookStack .env/IdP metadata; ADFS or Azure AD sending lowercase-encoded query params or unexpected NameID formats; replaying an old SAML response.","solutions":["Read the 'Reason:' suffix in the message — getLastErrorReason() names the exact validation failure; fix that specific mismatch first.","Verify the IdP x509 certificate in BookStack's SAML settings matches the signing cert currently used by the IdP (re-download IdP metadata after any rotation).","Sync server time (ntp/chrony) — clock skew is the most common cause of 'too old/not yet valid' SAML response errors.","Confirm entityId, ACS (callback) URL, and audience values match exactly between BookStack and the IdP configuration.","Enable SAML debug (SAML_DEBUG=true / toolkit debug setting) and compare the raw response against IdP logs to see which assertion constraint failed."],"exampleFix":"// before (.env, stale IdP cert)\nSAML2_IDP_x509=MIIC...old-cert...\n\n// after (.env, refreshed from current IdP metadata)\nSAML2_IDP_x509=MIIC...new-cert...","handlingStrategy":"try-catch","validationCode":"// Preconditions to verify before attempting ACS login:\n// 1. IdP cert in SP settings matches the current IdP signing cert\n// 2. Server clock in sync: chronyc tracking / ntpq -p (drift < ~1 min)\n// 3. entityId, ACS URL and audience match IdP-side SP configuration\n// 4. A fresh AuthNRequest was issued and its requestId matches the one passed in","typeGuard":null,"tryCatchPattern":"use OneLogin\\Saml2\\Error as Saml2Error;\n\ntry {\n    $user = $saml2Service->processAcsResponse($requestId, $samlResponse);\n} catch (Saml2Error $e) {\n    // Message contains 'Errors: ...; Reason: ...' — log the reason for IdP support tickets\n    report($e);\n    abort(500, 'SAML login failed validation. Check the Reason in logs and IdP cert/clock settings.');\n}","preventionTips":["Run NTP/chrony on the SP host — clock skew is the top cause of SAML response rejection","Re-import IdP metadata whenever the IdP rotates its signing certificate","Keep entityId/ACS/audience values identical on both sides; diff them after any change","Enable SAML toolkit debug logging in staging to catch validation mismatches early","Never cache or replay SAMLResponse POSTs"],"tags":["php","saml","sso","authentication","onelogin-saml"],"backgroundTag":"saml-response-validation-failed","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}