{"id":387881,"date":"2026-10-26T12:09:52","date_gmt":"2026-10-26T12:09:52","guid":{"rendered":"https:\/\/sv.wordpress.org\/plugins\/signclave-sso-for-saml-and-ldap\/"},"modified":"2026-10-26T18:36:17","modified_gmt":"2026-10-26T18:36:17","slug":"signclave-sso","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/signclave-sso\/","author":23556956,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.0.2","stable_tag":"1.0.2","tested":"7.1.4","requires":"6.0","requires_php":"8.2","requires_plugins":null,"header_name":"Signclave \u2013 SSO for SAML and LDAP","header_author":"Consid Bor\u00e5s","header_description":"Secure SAML SSO + LDAP authentication for WordPress.","assets_banners_color":"0c153b","last_updated":"2026-10-26 18:36:17","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/Considbrs-Webdev\/signclave-sso","header_author_uri":"https:\/\/profiles.wordpress.org\/considboras\/","rating":0,"author_block_rating":0,"active_installs":0,"downloads":93,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.0":{"tag":"1.0.0","author":"considboras","date":"2026-10-26 12:09:40","revision":3766350},"1.0.1":{"tag":"1.0.1","author":"considboras","date":"2026-10-26 13:25:31","revision":3766504},"1.0.2":{"tag":"1.0.2","author":"considboras","date":"2026-10-26 18:36:17","revision":3767114}},"upgrade_notice":[],"ratings":[],"assets_icons":{"icon-256x256.jpg":{"filename":"icon-256x256.jpg","revision":3767114,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.jpg":{"filename":"banner-1544x500.jpg","revision":3767114,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.jpg":{"filename":"banner-772x250.jpg","revision":3767114,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.0.0","1.0.1","1.0.2"],"block_files":[],"assets_screenshots":[],"screenshots":[]},"plugin_section":[],"plugin_tags":[2463,2464,9213,5136,2469],"plugin_category":[],"plugin_contributors":[288685],"plugin_business_model":[],"class_list":["post-387881","plugin","type-plugin","status-publish","hentry","plugin_tags-active-directory","plugin_tags-ldap","plugin_tags-saml","plugin_tags-single-sign-on","plugin_tags-sso","plugin_contributors-considboras","plugin_committers-considboras"],"banners":{"banner":"https:\/\/ps.w.org\/signclave-sso\/assets\/banner-772x250.jpg?rev=3767114","banner_2x":"https:\/\/ps.w.org\/signclave-sso\/assets\/banner-1544x500.jpg?rev=3767114","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/signclave-sso\/assets\/icon-256x256.jpg?rev=3767114","icon_2x":"https:\/\/ps.w.org\/signclave-sso\/assets\/icon-256x256.jpg?rev=3767114","generated":false},"screenshots":[],"raw_content":"<!--section=description-->\n<p>WordPress SAML Service Provider with optional LDAP fallback.<\/p>\n\n<p>Source: https:\/\/github.com\/Considbrs-Webdev\/signclave-sso<\/p>\n\n<p>Features:\n- SAML SSO redirect on <code>wp-login.php<\/code>\n- Signed assertion validation (XML signature + digest)\n- Replay protection, signed RelayState, redirect allowlisting\n- Optional LDAP authentication fallback\n- Optional group-to-role mapping\n- Security audit logging<\/p>\n\n<h3>Security<\/h3>\n\n<p>This plugin follows WordPress and OWASP-oriented practices:<\/p>\n\n<ul>\n<li>SAML signatures verified only against the configured IdP X.509 certificate (pinned).<\/li>\n<li>Strict SAML validation (audience, destination, issuer, InResponseTo) enabled by default.<\/li>\n<li>Emergency local login requires a secret key (<code>?password=1&amp;signclave_sso_key=...<\/code>), not a public flag.<\/li>\n<li>Proxy HTTPS headers are ignored unless explicitly enabled.<\/li>\n<li>Account linking by email is off by default.<\/li>\n<li>LDAP bind passwords encrypted at rest (AES-256-GCM).<\/li>\n<li>When LDAP is enabled and fully configured, SAML must verify the directory before provisioning a <strong>new<\/strong> WordPress user. Existing WordPress accounts can still sign in via SAML if LDAP is down.<\/li>\n<li>With LDAP disabled or incomplete, SAML runs alone (JIT from IdP attributes; local accounts match by username).<\/li>\n<li>No raw SQL; admin actions require <code>manage_options<\/code> and nonces.<\/li>\n<li>Audit log redacts secrets; log directory denied via <code>.htaccess<\/code>.<\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Upload <code>signclave-sso<\/code> to <code>\/wp-content\/plugins\/<\/code>.<\/li>\n<li>Activate the plugin.<\/li>\n<li>Configure SAML (and optionally LDAP) under <strong>Settings \u2192 Signclave SSO<\/strong>.<\/li>\n<li>Import SP metadata in your IdP (e.g. F5 APM).<\/li>\n<li>Copy the <strong>Emergency local login<\/strong> URL from Security settings.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"where%20are%20the%20settings%3F\"><h3>Where are the settings?<\/h3><\/dt>\n<dd><p><strong>Settings \u2192 Signclave SSO<\/strong> (<code>options-general.php?page=signclave-sso<\/code>). There is no top-level admin menu.<\/p><\/dd>\n<dt id=\"how%20do%20local%20wordpress%20admins%20log%20in%20when%20sso%20is%20enabled%3F\"><h3>How do local WordPress admins log in when SSO is enabled?<\/h3><\/dt>\n<dd><p>Use the emergency URL shown under <strong>Security<\/strong> in plugin settings. It includes a secret key. Existing WordPress accounts can also sign in via SAML when the IdP <code>uid<\/code> matches <code>user_login<\/code>; after the first login identity is the stored NameID.<\/p><\/dd>\n<dt id=\"does%20saml%20require%20ldap%3F\"><h3>Does SAML require LDAP?<\/h3><\/dt>\n<dd><p>No. Leave <strong>Enable LDAP fallback authentication<\/strong> unchecked for SAML-only sites. Turn it on only when you want directory verification for new account creation and LDAP password login.<\/p><\/dd>\n<dt id=\"does%20this%20plugin%20contact%20external%20servers%3F\"><h3>Does this plugin contact external servers?<\/h3><\/dt>\n<dd><p>Yes. Browser SSO sends the user to your <strong>Identity Provider<\/strong> (for example F5 APM). When LDAP is enabled, the WordPress server connects to your <strong>directory<\/strong> (ldaps:\/\/ recommended) for binds and searches. No traffic is sent to the plugin author or to wordpress.org for authentication.<\/p><\/dd>\n<dt id=\"behind%20a%20reverse%20proxy%20%2F%20load%20balancer%3F\"><h3>Behind a reverse proxy \/ load balancer?<\/h3><\/dt>\n<dd><p>Enable <strong>Trust proxy HTTPS headers<\/strong> only if the proxy is trusted and strips client-supplied headers.<\/p><\/dd>\n<dt id=\"how%20do%20i%20hide%20the%20plugin%20log%20directory%20on%20nginx%3F\"><h3>How do I hide the plugin log directory on Nginx?<\/h3><\/dt>\n<dd><p>Apache gets <code>.htaccess<\/code> automatically. On Nginx, deny <code>wp-content\/uploads\/signclave-sso\/<\/code> in the server block. See <code>docs\/instructions.md<\/code> in the plugin folder.<\/p><\/dd>\n<dt id=\"what%20if%20wp-admin%20is%20locked%20out%3F\"><h3>What if wp-admin is locked out?<\/h3><\/dt>\n<dd><p>From the WordPress root, with the plugin active: <code>wp signclave sso disable<\/code> turns off auto-redirect to the IdP. <code>wp signclave rotate-key<\/code> prints a new emergency login URL. Full command list is in <code>docs\/instructions.md<\/code>.<\/p><\/dd>\n<dt id=\"where%20is%20the%20f5%20%2F%20edirectory%20profile%20documented%3F\"><h3>Where is the F5 \/ eDirectory profile documented?<\/h3><\/dt>\n<dd><p>docs\/f5-apm.md ships with the plugin, including the staging checks. The IdP SSO URL must be the <strong>HTTP-POST<\/strong> endpoint; the plugin does not send HTTP-Redirect AuthnRequests.<\/p><\/dd>\n<dt id=\"which%20filters%20are%20supported%3F\"><h3>Which filters are supported?<\/h3><\/dt>\n<dd><p>These four filters are the public extension API (signatures will not change in patch releases):<\/p>\n\n<ul>\n<li><code>signclave_sso_map_attributes<\/code> \u2014 <code>( array $mapped, array $rawAttrs )<\/code>. <code>$mapped<\/code> keys: <code>username<\/code>, <code>email<\/code>, <code>first<\/code>, <code>last<\/code>, <code>groups<\/code>, <code>nameId<\/code>. Runs after SAML attribute extraction, before LDAP enrich. <code>nameId<\/code> is informational (identity stays the signed assertion NameID).<\/li>\n<li><code>signclave_sso_user_roles<\/code> \u2014 <code>( string[] $roles, WP_User $user, string[] $groups, string $source )<\/code>. Last word on roles after group mapping.<\/li>\n<li><code>signclave_sso_allow_login<\/code> \u2014 <code>( true|WP_Error $allow, WP_User $user, array $context )<\/code>. Return a <code>WP_Error<\/code> to reject a valid SAML assertion before the auth cookie is set.<\/li>\n<li><p><code>signclave_sso_authn_request<\/code> \u2014 <code>( string $xml, array $saml )<\/code>. Adjust the outbound AuthnRequest XML before it is POSTed to the IdP.<\/p>\n\n<p>signclave_sso_trust_proxy_headers remains available for proxy HTTPS detection.<\/p><\/li>\n<\/ul><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.0.2<\/h4>\n\n<ul>\n<li>Directory banner and icon.<\/li>\n<\/ul>\n\n<h4>1.0.1<\/h4>\n\n<ul>\n<li>Source repository is github.com\/Considbrs-Webdev\/signclave-sso. The directory contributor is <code>considboras<\/code>.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>First release on wordpress.org.<\/li>\n<\/ul>\n\n<h4>0.1.21<\/h4>\n\n<ul>\n<li>Plugin Check: print the SAML auto-submit through the script API, list <code>considboras<\/code> as a contributor, and delete only <code>signclave_sso_<\/code> transients on uninstall.<\/li>\n<\/ul>\n\n<h4>0.1.20<\/h4>\n\n<ul>\n<li>Plugin Check: WordPress options, query parameters, AJAX, user meta, cron, and transients use the <code>signclave_sso_<\/code> prefix. Existing <code>sls_options<\/code> are still read until the first save.<\/li>\n<\/ul>\n\n<h4>0.1.19<\/h4>\n\n<ul>\n<li>Plugin Check: global constants use the <code>SIGNCLAVE_SSO_<\/code> prefix.<\/li>\n<\/ul>\n\n<h4>0.1.18<\/h4>\n\n<ul>\n<li>Plugin Check: ignore exception-message escaping. Those strings are logged or thrown, and the browser only sees a fixed escaped message.<\/li>\n<\/ul>\n\n<h4>0.1.17<\/h4>\n\n<ul>\n<li>Rename the plugin to Signclave \u2013 SSO for SAML and LDAP. Slug and text domain are <code>signclave-sso<\/code>. WP-CLI is <code>wp signclave<\/code>.<\/li>\n<li>Existing settings in <code>sls_options<\/code> are read until the first save, which stores <code>sc_options<\/code>.<\/li>\n<li>Public filters use the <code>signclave_sso_<\/code> prefix.<\/li>\n<\/ul>\n\n<h4>0.1.16<\/h4>\n\n<ul>\n<li>Update dependencies<\/li>\n<\/ul>\n\n<h4>0.1.15<\/h4>\n\n<ul>\n<li>PHPStan: treat <code>node_modules<\/code> as an optional exclude so CI can run without npm install.<\/li>\n<\/ul>\n\n<h4>0.1.14<\/h4>\n\n<ul>\n<li>CI: drop GitHub Actions Composer oauth before Strauss; use actions\/checkout@v5 (Node 24).<\/li>\n<\/ul>\n\n<h4>0.1.13<\/h4>\n\n<ul>\n<li>CI: lock Composer to PHP 8.2 so the lockfile installs on the 8.2 runner; ignore GitHub Actions tokens when running Strauss.<\/li>\n<\/ul>\n\n<h4>0.1.12<\/h4>\n\n<ul>\n<li>Settings screen: show WordPress\u2019s \u201cSettings saved\u201d notice once (remove the duplicate plugin notice).<\/li>\n<\/ul>\n\n<h4>0.1.11<\/h4>\n\n<ul>\n<li>SAML first login may bind to an existing WordPress <code>user_login<\/code> (LDAP-optional 0.1.3 rule). Afterwards identity stays the stored NameID. Email still cannot choose the SAML account.<\/li>\n<li>When Automatic SSO is off, wp-login.php shows a Log in with SSO button below the form.<\/li>\n<\/ul>\n\n<h4>0.1.10<\/h4>\n\n<ul>\n<li>Plugin Check: zero errors against the wordpress.org zip surface (<code>bin\/plugin-check.sh<\/code>, <code>.distignore<\/code>).<\/li>\n<li>Sanitize <code>wp_unslash<\/code> on <code>REMOTE_ADDR<\/code> and trusted forwarded HTTPS headers.<\/li>\n<\/ul>\n\n<h4>0.1.9<\/h4>\n\n<ul>\n<li>Tests: Pest Unit + Security, PHPStan level 8, type coverage 100 %, GitHub Actions (PHP 8.2\/8.4).<\/li>\n<li>SAML: reject transient NameID; unspecified remains the happy path (F5 profile).<\/li>\n<li>Roles: do not map slugs missing from <code>wp_roles()<\/code>. Uninstall deletes <code>sc_*<\/code> transients.<\/li>\n<li>Docs: F5 checklist and default attribute\/NameID profile. Captured F5 XML fixture still deferred.<\/li>\n<\/ul>\n\n<h4>0.1.8<\/h4>\n\n<ul>\n<li>Settings API (<code>options.php<\/code>) and menu under <strong>Settings<\/strong> (not a top-level item).<\/li>\n<li>i18n: POT + Swedish <code>sv_SE<\/code>; <code>npm run translate*<\/code>.<\/li>\n<li>Public filters: <code>sc_map_attributes<\/code>, <code>sc_user_roles<\/code>, <code>sc_allow_login<\/code>, <code>sc_authn_request<\/code>.<\/li>\n<li>WP-CLI: <code>wp signclave sso<\/code>, <code>rotate-key<\/code>, <code>test-ldap<\/code>, <code>test-saml<\/code>, <code>doctor<\/code>.<\/li>\n<li>Daily log retention (default 90 days), Site Health tests, privacy\/threat-model\/F5 docs.<\/li>\n<\/ul>\n\n<h4>0.1.7<\/h4>\n\n<ul>\n<li>Security: xmlseclibs (Strauss-prefixed) replaces custom XML-DSig; SHA-1 rejected; assertion must be signed.<\/li>\n<li>Security: NameID identity binding, comment-injection protection, dual IdP certs, SAMLResponse size limit.<\/li>\n<li>Security: configurable role downgrade (on by default), protected logins, last-admin guard.<\/li>\n<li>Ops: LDAP host failover, randomized log filenames, WP_Filesystem on activation, clearer bind-secret decrypt errors.<\/li>\n<li>Emergency login POST preserves bypass key; IdP-initiated SSO opt-in (off by default).<\/li>\n<\/ul>\n\n<h4>0.1.6<\/h4>\n\n<ul>\n<li>Internal architecture: PSR-4 (<code>Signclave\\<\/code>), config helpers, instance DI, Vite-built admin assets in <code>dist\/<\/code>. Login behavior unchanged from 0.1.5.<\/li>\n<\/ul>\n\n<h4>0.1.5<\/h4>\n\n<ul>\n<li>Version bump.<\/li>\n<\/ul>\n\n<h4>0.1.4<\/h4>\n\n<ul>\n<li>Code structure and WordPress coding hygiene: settings view, diagnostics class, shared LDAP connection helper, i18n on admin UI. Login behavior unchanged from 0.1.3.<\/li>\n<\/ul>\n\n<h4>0.1.3<\/h4>\n\n<ul>\n<li>LDAP is optional by default. SAML works without a directory; existing WordPress accounts can sign in via SAML even if LDAP is unavailable. LDAP is required only when creating new users and LDAP is fully configured.<\/li>\n<\/ul>\n\n<h4>0.1.2<\/h4>\n\n<ul>\n<li>SAML login always verifies LDAP when LDAP is enabled (mandatory, not optional).<\/li>\n<\/ul>\n\n<h4>0.1.1<\/h4>\n\n<ul>\n<li>Optional LDAP existence check on every SAML login.<\/li>\n<\/ul>\n\n<h4>0.1.0<\/h4>\n\n<ul>\n<li>Security hardening: pinned IdP cert, strict SAML validation, secret emergency login, opt-in proxy trust.<\/li>\n<li>Account linking by email disabled by default.<\/li>\n<li>Improved uninstall cleanup.<\/li>\n<\/ul>\n\n<h4>0.0.2<\/h4>\n\n<ul>\n<li>F5-compatible SAML digest verification and ACS error handling.<\/li>\n<\/ul>\n\n<h4>0.0.1<\/h4>\n\n<ul>\n<li>Initial release.<\/li>\n<\/ul>","raw_excerpt":"Secure SAML 2.0 SP with optional LDAP, using pinned IdP certificates and prefixed xmlseclibs.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/387881","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=387881"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/considboras"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=387881"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=387881"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=387881"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=387881"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=387881"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=387881"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}