{"id":361711,"date":"2026-10-14T18:20:18","date_gmt":"2026-10-14T18:20:18","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/leadstart-sso\/"},"modified":"2026-10-14T18:16:10","modified_gmt":"2026-10-14T18:16:10","slug":"cross-site-sso-for-openid-connect","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/cross-site-sso-for-openid-connect\/","author":20839523,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.0.0","stable_tag":"1.0.0","tested":"7.1.3","requires":"5.9","requires_php":"7.4","requires_plugins":null,"header_name":"Cross-Site SSO for OpenID Connect","header_author":"Leadstart Media, Inc.","header_description":"Silent single sign-on, logout and shared order history across separate WordPress installs connected using one OpenID Connect provider. This plugin requires the OpenID Connect Generic plugin.","assets_banners_color":"","last_updated":"2026-10-14 18:16:10","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/leadstartorg\/cross-site-sso-for-openid-connect","header_author_uri":"https:\/\/leadstart.org","rating":0,"author_block_rating":0,"active_installs":0,"downloads":65,"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":"leadstartorg","date":"2026-10-14 18:16:10","revision":3746916}},"upgrade_notice":[],"ratings":[],"assets_icons":[],"assets_banners":[],"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.0.0"],"block_files":[],"assets_screenshots":[],"screenshots":[]},"plugin_section":[],"plugin_tags":[162353,43290,5136,2469,286],"plugin_category":[45],"plugin_contributors":[286801],"plugin_business_model":[],"class_list":["post-361711","plugin","type-plugin","status-publish","hentry","plugin_tags-oidc","plugin_tags-openid-connect","plugin_tags-single-sign-on","plugin_tags-sso","plugin_tags-woocommerce","plugin_category-ecommerce","plugin_contributors-leadstartorg","plugin_committers-leadstartorg"],"banners":[],"icons":{"svg":false,"icon":"https:\/\/s.w.org\/plugins\/geopattern-icon\/cross-site-sso-for-openid-connect.svg","icon_2x":false,"generated":true},"screenshots":[],"raw_content":"<!--section=description-->\n<p>The plugin is an addon to the <strong>OpenID Connect Generic<\/strong> plugin which authenticates a user on each site.<\/p>\n\n<p><strong>Single sign-on.<\/strong> The plugin performs an OpenID Connect authorization against your provider. If the provider session exists, the visitor is signed in with no click and no password prompt.<\/p>\n\n<p><strong>Single logout.<\/strong> If you logout on a site you are logged out of them all. This happens through a signed top-level redirect chain that visits each configured site in turn and\nends at your provider's end-session endpoint.<\/p>\n\n<p><strong>Order history.<\/strong> The sites without WooCommerce can display a signed-in customer's order history with the <code>[cross_site_orders]<\/code> shortcode.<\/p>\n\n<h4>Design notes<\/h4>\n\n<p>This plugin deliberately does not replicate users, passwords, or orders between\nsites. Your identity provider is the source of truth for identity; the site with WooCommerce is the source of truth for orders.<\/p>\n\n<h4>Roles and sign-on<\/h4>\n\n<p>By default, silent authentication is disabled for administrators to prevent the exploitation of hijacked provider sessions; however, administrative access to the plugin remains fully available. If your organization requires administrative roles to be included in Single Sign-On (SSO), you can modify this behavior using the LS_SSO_BLOCK_SILENT_ROLES constant (configured as a comma-separated list of role slugs, or left empty to permit all roles). Alternatively, you can utilize the ls_sso_block_silent_roles filter.<\/p>\n\n<h4>Requirements<\/h4>\n\n<ul>\n<li>The <a href=\"https:\/\/wordpress.org\/plugins\/daggerhart-openid-connect-generic\/\">OpenID Connect Generic<\/a> plugin, active and configured, on every participating site.<\/li>\n<li>An OpenID Connect provider (Auth0, Keycloak, Okta, Entra ID and others).<\/li>\n<li>HTTPS on every site.<\/li>\n<li>WooCommerce, on the store site only if you want order data.<\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Install and configure <strong>OpenID Connect Generic<\/strong> on every site first, and\nconfirm that signing in works on each one individually.<\/li>\n<li>Install and activate this plugin on every site.<\/li>\n<li>Generate one shared secret, to be used by all sites:\n   php -r \"echo bin2hex(random_bytes(32)), PHP_EOL;\"\nThe plugin's settings screen also has a <strong>Generate<\/strong> button that produces one\nin your browser without sending it to any server.<\/li>\n<li><p>Configure each site, either in <code>wp-config.php<\/code> (preferred) or under\n<strong>Tools &gt; Cross-Site SSO &gt; Settings<\/strong> if you cannot edit that file.\n   LS_SSO_PEERS lists the <em>other<\/em> sites, so it differs per site, the secret and\nthe store are identical on all sites:<\/p>\n\n<p>define( 'LS_SSO_SECRET', 'your-64-character-hex-secret' );\n   define( 'LS_SSO_PEERS',  'https:\/\/second-site.com,https:\/\/third-site.com' );\n   define( 'LS_SSO_STORE',  'https:\/\/store-site.com' );<\/p>\n\n<p>Sites may mix the two methods as long as the secret matches.<\/p><\/li>\n<li><p>Visit <strong>Tools \u2192 Cross-Site SSO<\/strong> and run <strong>Test peers<\/strong> on each site.<\/p><\/li>\n<li>In OpenID Connect Generic, enable <strong>Alternate Redirect URI<\/strong>, then re-save\nSettings \u2192 Permalinks. Register each site's <code>\/openid-connect-authorize<\/code> URL\nwith your provider.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"where%20should%20the%20shared%20secret%20live%3F\"><h3>Where should the shared secret live?<\/h3><\/dt>\n<dd><p>In <code>wp-config.php<\/code>, if you can edit. A signing key in the options table is in every database backup and every export handed to a contractor, and is reachable by any vulnerability that discloses options.<\/p>\n\n<p>If you cannot edit that file, many managed hosts allow plugin installation and no file access at all, set it under <strong>Tools &gt; Cross-Site SSO &gt; Settings<\/strong> instead. The option is stored with autoload off and is never displayed again after saving. A constant, when defined, always wins and the field becomes read-only.<\/p>\n\n<p>The screen shows a short fingerprint of the secret, so you can confirm every site holds the same value. When the fingerprints match, this mean matching secrets; while a mismatch explains the 403 errors.<\/p><\/dd>\n<dt id=\"what%20does%20the%20status%20codes%20that%20mean%3F\"><h3>What does the status codes that mean?<\/h3><\/dt>\n<dd><p>The status code is the diagnosis, and these are unrelated:<\/p>\n\n<ul>\n<li><strong>404<\/strong> \u2014 no REST route on that site, so the plugin is not installed or not activated.<\/li>\n<li><strong>503<\/strong> \u2014 the plugin is installed but has no secret or not configured yet.<\/li>\n<li><strong>403<\/strong> \u2014 the request was signed, received, but refused. <\/li>\n<\/ul>\n\n<p>You have to work through the sites in order: install and activate on sites, then configure and test.<\/p><\/dd>\n<dt id=\"can%20i%20mix%20wp-config.php%20constants%20and%20the%20settings%20tab%20across%20sites%3F\"><h3>Can I mix wp-config.php constants and the Settings tab across sites?<\/h3><\/dt>\n<dd><p>Yes, each site resolves each setting independently: the constant if one is defined, the stored option otherwise. A site with file access can use constants while another uses the Settings screen, as long as the secret matches, compare the fingerprints to confirm.<\/p><\/dd>\n<dt id=\"do%20all%20sites%20need%20the%20same%20plugin%20version%3F\"><h3>Do all sites need the same plugin version?<\/h3><\/dt>\n<dd><p>They interoperate across versions: the signing format, headers, REST routes and logout tickets have not changed since 1.0.0. When running the same version everywhere is still recommended, because newer versions add local behavior (for example, refusing silent sign-on for administrators) that only applies where installed.<\/p><\/dd>\n<dt id=\"can%20i%20install%20it%20in%20mu-plugins%20on%20some%20sites%20and%20as%20a%20normal%20plugin%20on%20others%3F\"><h3>Can I install it in mu-plugins on some sites and as a normal plugin on others?<\/h3><\/dt>\n<dd><p>Yes, per site. What you must not do is install it in <strong>both<\/strong> places on the <strong>same<\/strong> site: the classes then load twice from two paths, which is a fatal error. The plugin detects this, stops loading, and shows an admin notice naming the file that is actually running.<\/p><\/dd>\n<dt id=\"can%20i%20run%20this%20on%20a%20host%20with%20no%20sftp%20access%3F\"><h3>Can I run this on a host with no SFTP access?<\/h3><\/dt>\n<dd><p>Yes, every setting can be entered on the Settings tab. The plugin only needs to be installable and able to make outbound HTTPS requests.<\/p><\/dd>\n<dt id=\"what%20role%20do%20new%20users%20get%3F\"><h3>What role do new users get?<\/h3><\/dt>\n<dd><p>Whatever <strong>Settings &gt; General &gt; New User Default Role<\/strong> says on that site normally <code>subscriber<\/code>. OpenID Connect Generic does not set a role when it creates the account, so <code>wp_insert_user()<\/code> falls back to that option., changing the setting is usually the whole answer, no code is needed.<\/p>\n\n<p>In order to decide the role per user instead, use the <code>ls_sso_map_roles<\/code> filter. The plugin runs on every SSO login, including when no role claim is configured:<\/p>\n\n<pre><code>add_filter( 'ls_sso_map_roles', function ( $roles, $incoming, $user ) {\n    return in_array( 'staff', $incoming, true ) ? array( 'editor' ) : array( 'customer' );\n}, 10, 3 );\n<\/code><\/pre>\n\n<p>Administrators are never reassigned by this filter, so a misconfigured provider cannot demote a user.<\/p>\n\n<p>If you map from a provider claim, note that <strong>Auth0 silently drops custom claims\nnamespaced under an Auth0 domain<\/strong> \u2014 <code>auth0.com<\/code>, <code>webtask.io<\/code> and <code>webtask.run<\/code>\nare all rejected as namespaces. Please use a domain you control. A claim namespaced to <code>your-tenant.auth0.com<\/code> never\narrives, and role mapping then appears broken with no error.<\/p><\/dd>\n<dt id=\"does%20this%20require%20woocommerce%3F\"><h3>Does this require WooCommerce?<\/h3><\/dt>\n<dd><p>No, WooCommerce is only needed for the optional order-history feature, and only\non the one site that runs the store.<\/p><\/dd>\n<dt id=\"does%20this%20work%20with%20wordpress%20multisite%3F\"><h3>Does this work with WordPress Multisite?<\/h3><\/dt>\n<dd><p>The plugin is built for the opposite case: separate installations with separate\ndatabases. A multisite network already shares users, sessions and logout through\nWordPress core, and does not need this plugin.<\/p><\/dd>\n<dt id=\"what%20happens%20when%20a%20peer%20site%20is%20offline%3F\"><h3>What happens when a peer site is offline?<\/h3><\/dt>\n<dd><p>All updates are queued and retried with backoff, using Action Scheduler when\navailable and WP-Cron otherwise. The order history requests uses a timeout and a\ncached result, so a slow WooCommerce site never blocks a page for a visitor.<\/p><\/dd>\n<dt id=\"does%20sso%20redirect%20every%20visitor%2C%20including%20search%20engines%3F\"><h3>Does SSO redirect every visitor, including search engines?<\/h3><\/dt>\n<dd><p>No, crawlers are excluded, the check runs at most once per browser per 30\nminutes, and by default the check is performed in the browser rather than as a\nserver-side redirect, so it cannot be captured by a page cache.<\/p><\/dd>\n<dt id=\"what%20personal%20data%20does%20this%20store%3F\"><h3>What personal data does this store?<\/h3><\/dt>\n<dd><p>The plugin stores no personal data of its own. The activity log records the\nevent type, the peer site, the route, a WordPress user ID, and success or\nfailure and never request bodies, meta values, tokens, or signatures. Rows are\ndeleted automatically after 30 days, filterable via <code>ls_sso_log_retention_days<\/code>.<\/p>\n\n<p>When uninstalling the plugin, it removes the log table entirely.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release: SSO, single logout, order history.<\/li>\n<\/ul>","raw_excerpt":"The plugin provides a single sign-on, logout and signed cross-site connection for separate WordPress installs sharing one OpenID Connect provider.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/361711","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=361711"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/leadstartorg"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=361711"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=361711"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=361711"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=361711"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=361711"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=361711"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}