When Evolution CMS is installed or updated via the CLI (php core/artisan make:site update, or any php artisan migrate --force), the seiger/smultisite migration seeds the default multisite record with domain = "localhost". After this, logging into /manager/ from the real domain (e.g. local-test.evo) triggers the sMultisite SSO flow, which redirects the browser to http://localhost/_ms-sso/?c=<jwt>.... Because localhost does not serve the CMS in a typical local-dev setup, the SSO handshake never completes and the manager becomes inaccessible.
Steps to Reproduce
- Install Evolution CMS 3.5.7
- Configure a vhost for a non-localhost domain, e.g.
local-test.evo, pointing to the project root.
- Open
http://local-test.evo/manager/ in a browser.
- Enter valid admin credentials and submit.
Observed: the browser is redirected to
http://localhost/_ms-sso/?c=eyJ...&return=http%3A%2F%2Flocal-test.evo%2F_ms-run%2F...
and the request fails (404 / connection refused), because Apache on localhost serves the OSPanel default page, not Evolution CMS.
Expected: login completes on local-test.evo and the manager welcome page is shown.
Root Cause
In the package migration
core/vendor/seiger/smultisite/database/migrations/2023_08_31_150405_create_s_multisites_table.php (line 36):
'domain' => get_by_key($_SERVER, 'HTTP_HOST', 'localhost'),
get_by_key (defined in core/functions/helper.php:72) returns the fallback 'localhost' whenever $_SERVER['HTTP_HOST'] is missing. Migrations executed through Artisan run in the CLI SAPI, where HTTP_HOST is not populated — confirmed: under CLI getenv('HTTP_HOST') returns false. As a result, the seeded multisite record is always domain = "localhost" when migrations are run from the console.
The web installer (which runs through install/index.php under a real HTTP request) populates HTTP_HOST correctly, so the issue is specific to the CLI update / migrate --force path.
How the SSO flow then breaks
The sMultisite plugin
(core/vendor/seiger/smultisite/plugins/sMultisitePlugin.php)
listens to the evolution.OnManagerLogin event (line 379). After a successful credential check:
$home is derived from the current request host ($_SERVER['HTTP_HOST'], line 392), i.e. local-test.evo.
$targets is computed as every domain from evo_s_multisites that is not equal to $home and not empty (lines 402–409). With the only seeded row being localhost, this yields ['localhost'].
- A signed SSO token is built for each target via
ms_sso_token_make()
(core/vendor/seiger/smultisite/functions/sso.php:75),
embedding mode, sid, sid_native, uid, and host (= localhost) into a JWT.
- The run plan is persisted with
ms_run_put() and the runId is stored in $_SESSION['ms_run_login'] (lines 427–431).
- On the next manager render, the
OnManagerPageInit handler (line 533) calls ms_sso_resolve_start_url() (lines 77–161), which reads the plan and issues a Location: http://localhost/_ms-sso/?c=<jwt>&return=... redirect (line 547).
localhost does not serve the CMS, so the receiver at SsoController::receiveLogin() (src/Controllers/SsoController.php:85) never runs. The user is left on a 404 / error page.
Suggested Fixes
-
Migration default (preferred): In 2023_08_31_150405_create_s_multisites_table.php, detect the CLI context and either skip seeding (letting the admin configure the domain later) or read the host from core/custom/.env (e.g. a new APP_URL/SITE_URL key) instead of falling back to localhost. For example:
$host = get_by_key($_SERVER, 'HTTP_HOST', '');
if ($host === '' && function_exists('env')) {
$envUrl = (string) env('APP_URL', '');
$host = $envUrl !== '' ? (string) parse_url($envUrl, PHP_URL_HOST) : '';
}
'domain' => $host !== '' ? $host : 'localhost',
-
Skip SSO when no real cross-domain targets exist: In OnManagerLogin, bail out when targets is empty OR matches the default/placeholder value. With only localhost in the table and $home already being localhost (or any other domain), the user is redirected to a host that is not actually a separate site, which is never useful.
-
Guard against unreachable targets: Fall back to the standard (non-SSO) manager login when the configured targets are not part of allowedHosts() or when the receiver is unreachable.
Files Involved
| File |
Role |
core/vendor/seiger/smultisite/database/migrations/2023_08_31_150405_create_s_multisites_table.php:36 |
Seeds domain with HTTP_HOST/localhost fallback |
core/functions/helper.php:72 |
get_by_key returns fallback when key is missing |
core/src/Console/SiteUpdateCommand.php:248-252 |
Runs php artisan migrate --force under CLI SAPI |
core/vendor/seiger/smultisite/plugins/sMultisitePlugin.php:379-439 |
OnManagerLogin — builds SSO run plan over all domains != current |
core/vendor/seiger/smultisite/plugins/sMultisitePlugin.php:77-161 |
ms_sso_resolve_start_url() — issues the first redirect to _ms-sso on the target host |
core/vendor/seiger/smultisite/plugins/sMultisitePlugin.php:533-549 |
OnManagerPageInit — fires the HTTP 303 redirect |
core/vendor/seiger/smultisite/functions/sso.php:74-83 |
ms_sso_token_make() — embeds target host claim in JWT |
core/vendor/seiger/smultisite/src/Controllers/SsoController.php:85-136 |
receiveLogin() — never reached because localhost does not serve the CMS |
core/vendor/seiger/smultisite/src/Http/routes.php |
Routes for _ms-run / _ms-sso endpoints |
Workaround (until fixed)
After a CLI install/upgrade, update the seeded multisite row to the real domain:
php core/artisan tinker
>>> \Seiger\sMultisite\Models\sMultisite::where('key','default')->update(['domain' => 'local-test.evo']);
or via SQL:
UPDATE evo_s_multisites SET domain = 'local-test.evo' WHERE `key` = 'default';
Then clear cookies for both localhost and the real domain, and re-open /manager/.
Environment: Evolution CMS 3.5.7 · seiger/smultisite (bundled) · PHP 8.4 · SQLite (also affects MySQL) · Local dev (OSPanel / Apache, analogous to Laragon/XAMPP).
When Evolution CMS is installed or updated via the CLI (
php core/artisan make:site update, or anyphp artisan migrate --force), theseiger/smultisitemigration seeds the default multisite record withdomain = "localhost". After this, logging into/manager/from the real domain (e.g.local-test.evo) triggers the sMultisite SSO flow, which redirects the browser tohttp://localhost/_ms-sso/?c=<jwt>.... Becauselocalhostdoes not serve the CMS in a typical local-dev setup, the SSO handshake never completes and the manager becomes inaccessible.Steps to Reproduce
local-test.evo, pointing to the project root.http://local-test.evo/manager/in a browser.Observed: the browser is redirected to
http://localhost/_ms-sso/?c=eyJ...&return=http%3A%2F%2Flocal-test.evo%2F_ms-run%2F...and the request fails (404 / connection refused), because Apache on
localhostserves the OSPanel default page, not Evolution CMS.Expected: login completes on
local-test.evoand the manager welcome page is shown.Root Cause
In the package migration
core/vendor/seiger/smultisite/database/migrations/2023_08_31_150405_create_s_multisites_table.php(line 36):get_by_key(defined incore/functions/helper.php:72) returns the fallback'localhost'whenever$_SERVER['HTTP_HOST']is missing. Migrations executed through Artisan run in the CLI SAPI, whereHTTP_HOSTis not populated — confirmed: under CLIgetenv('HTTP_HOST')returnsfalse. As a result, the seeded multisite record is alwaysdomain = "localhost"when migrations are run from the console.The web installer (which runs through
install/index.phpunder a real HTTP request) populatesHTTP_HOSTcorrectly, so the issue is specific to the CLI update /migrate --forcepath.How the SSO flow then breaks
The sMultisite plugin
(
core/vendor/seiger/smultisite/plugins/sMultisitePlugin.php)listens to the
evolution.OnManagerLoginevent (line 379). After a successful credential check:$homeis derived from the current request host ($_SERVER['HTTP_HOST'], line 392), i.e.local-test.evo.$targetsis computed as everydomainfromevo_s_multisitesthat is not equal to$homeand not empty (lines 402–409). With the only seeded row beinglocalhost, this yields['localhost'].ms_sso_token_make()(
core/vendor/seiger/smultisite/functions/sso.php:75),embedding
mode,sid,sid_native,uid, andhost(=localhost) into a JWT.ms_run_put()and the runId is stored in$_SESSION['ms_run_login'](lines 427–431).OnManagerPageInithandler (line 533) callsms_sso_resolve_start_url()(lines 77–161), which reads the plan and issues aLocation: http://localhost/_ms-sso/?c=<jwt>&return=...redirect (line 547).localhostdoes not serve the CMS, so the receiver atSsoController::receiveLogin()(src/Controllers/SsoController.php:85) never runs. The user is left on a 404 / error page.Suggested Fixes
Migration default (preferred): In
2023_08_31_150405_create_s_multisites_table.php, detect the CLI context and either skip seeding (letting the admin configure the domain later) or read the host fromcore/custom/.env(e.g. a newAPP_URL/SITE_URLkey) instead of falling back tolocalhost. For example:Skip SSO when no real cross-domain targets exist: In
OnManagerLogin, bail out whentargetsis empty OR matches the default/placeholder value. With onlylocalhostin the table and$homealready beinglocalhost(or any other domain), the user is redirected to a host that is not actually a separate site, which is never useful.Guard against unreachable targets: Fall back to the standard (non-SSO) manager login when the configured targets are not part of
allowedHosts()or when the receiver is unreachable.Files Involved
core/vendor/seiger/smultisite/database/migrations/2023_08_31_150405_create_s_multisites_table.php:36domainwithHTTP_HOST/localhostfallbackcore/functions/helper.php:72get_by_keyreturns fallback when key is missingcore/src/Console/SiteUpdateCommand.php:248-252php artisan migrate --forceunder CLI SAPIcore/vendor/seiger/smultisite/plugins/sMultisitePlugin.php:379-439OnManagerLogin— builds SSO run plan over all domains != currentcore/vendor/seiger/smultisite/plugins/sMultisitePlugin.php:77-161ms_sso_resolve_start_url()— issues the first redirect to_ms-ssoon the target hostcore/vendor/seiger/smultisite/plugins/sMultisitePlugin.php:533-549OnManagerPageInit— fires the HTTP 303 redirectcore/vendor/seiger/smultisite/functions/sso.php:74-83ms_sso_token_make()— embeds targethostclaim in JWTcore/vendor/seiger/smultisite/src/Controllers/SsoController.php:85-136receiveLogin()— never reached becauselocalhostdoes not serve the CMScore/vendor/seiger/smultisite/src/Http/routes.php_ms-run/_ms-ssoendpointsWorkaround (until fixed)
After a CLI install/upgrade, update the seeded multisite row to the real domain:
or via SQL:
Then clear cookies for both
localhostand the real domain, and re-open/manager/.Environment: Evolution CMS 3.5.7 ·
seiger/smultisite(bundled) · PHP 8.4 · SQLite (also affects MySQL) · Local dev (OSPanel / Apache, analogous to Laragon/XAMPP).