{"record":{"id":"3902ca274ef780a7","repo":"BookStackApp/BookStack","slug":"errors-saml-no-email-address","errorCode":null,"errorMessage":"errors.saml_no_email_address","messagePattern":"errors\\.saml_no_email_address","errorType":"exception","errorClass":"SamlException","httpStatus":null,"severity":"error","filePath":"app/Access/Saml2Service.php","lineNumber":361,"sourceCode":"    public function processLoginCallback(string $samlID, array $samlAttributes): User\n    {\n        $userDetails = $this->getUserDetails($samlID, $samlAttributes);\n        $isLoggedIn = auth()->check();\n\n        if ($this->shouldSyncGroups()) {\n            $userDetails['groups'] = $this->getUserGroups($samlAttributes);\n        }\n\n        if ($this->config['dump_user_details']) {\n            throw new JsonDebugException([\n                'id_from_idp'         => $samlID,\n                'attrs_from_idp'      => $samlAttributes,\n                'attrs_after_parsing' => $userDetails,\n            ]);\n        }\n\n        if (empty($userDetails['email'])) {\n            throw new SamlException(trans('errors.saml_no_email_address'));\n        }\n\n        if ($isLoggedIn) {\n            throw new SamlException(trans('errors.saml_already_logged_in'), '/login');\n        }\n\n        $user = $this->registrationService->findOrRegister(\n            $userDetails['name'],\n            $userDetails['email'],\n            $userDetails['external_id']\n        );\n\n        if ($this->shouldSyncGroups()) {\n            $this->groupSyncService->syncUserWithFoundGroups($user, $userDetails['groups'], $this->config['remove_from_groups']);\n        }\n\n        $this->loginService->login($user, 'saml2');\n","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Access/Saml2Service.php#L343-L379","documentation":"SamlException thrown by BookStack's SAML2 login flow when the IdP response contains user details but no email address. BookStack requires an email to find or register the local user matching the SAML identity, so a response lacking the email attribute is rejected. The check runs in processLoginCallback after the SAML attributes have been parsed into $userDetails.","triggerScenarios":"The IdP sends a successful assertion whose attribute statement does not include an email attribute (e.g. the configured email attribute name in BookStack's saml2 idp settings does not match any attribute the IdP actually releases, or the IdP releases no attributes at all).","commonSituations":"IdP attribute mapping misconfiguration (email attribute name changed or set to a custom claim the IdP never sends), IdP attribute release policy filtering email for privacy, switching IdPs (e.g. Azure AD sends 'email' only for guest-allowed accounts) or upgrading an IdP that renamed its claims.","solutions":["Check the email attribute name configured for the SAML2 IdP in BookStack (Admin > SAML2 settings / 'External user id' style option mapping) and set it to the exact attribute the IdP releases (e.g. http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress or mail)","Verify in the IdP admin console that the email attribute/claim is included in the assertion for this service provider","Inspect the debug dump (attrs_from_idp / attrs_after_parsing logged when debug is enabled) to see which attributes actually arrived and adjust the mapping","If the IdP cannot send email, enable the 'email is determined from username/ID' style fallback by mapping email to an attribute that is always present"],"exampleFix":"// before (BookStack saml2 option: email attribute set to a claim the IdP doesn't send)\n$emailAttributeName = 'email';\n// after (match the IdP's actual claim name)\n$emailAttributeName = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress';","handlingStrategy":"validation","validationCode":"// Before invoking the SAML login flow, ensure the IdP attribute mapping yields an email\n// (e.g. run a test decode of a sample assertion or verify mapping config)\n$attrs = $samlAttributes; // attributes released by IdP\n$emailKey = config('saml2.email_attribute');\nif (empty($attrs[$emailKey][0] ?? null)) {\n    throw new \\RuntimeException(\"IdP does not release the '{$emailKey}' attribute; fix attribute mapping/release policy before login.\");\n}","typeGuard":"function samlDetailsHaveEmail(array $userDetails): bool\n{\n    return isset($userDetails['email']) && filter_var($userDetails['email'], FILTER_VALIDATE_EMAIL) !== false;\n}","tryCatchPattern":"use BookStack\\Exceptions\\SamlException;\n\ntry {\n    $saml->processLoginCallback();\n} catch (SamlException $e) {\n    if ($e->getMessage() === trans('errors.saml_no_email_address')) {\n        Log::warning('SAML login failed: IdP sent no email attribute', ['attributes' => $samlAttributes]);\n        return redirect('/login')->with('error', 'Your identity provider did not share an email address.');\n    }\n    throw $e;\n}","preventionTips":["Verify the email attribute name in SAML2 settings matches a claim your IdP actually releases (test with a real assertion)","Enable BookStack SAML debug (dump attributes) in a staging environment to confirm attribute payload before rollout","Ask the IdP admin to add the email claim to the SP's attribute release policy","Add an automated SSO smoke test that asserts the full attribute set, including email"],"tags":["saml2","authentication","attribute-mapping","configuration"],"backgroundTag":"saml-missing-email-attribute","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}