diff --git a/docs/GatewayConfiguration.md b/docs/GatewayConfiguration.md
index 0bfb769..07c1c04 100644
--- a/docs/GatewayConfiguration.md
+++ b/docs/GatewayConfiguration.md
@@ -148,6 +148,7 @@ the affected stream while the MXAccess session remains active.
| `MxGateway:Dashboard:Enabled` | `true` | Enables Blazor Server dashboard route mapping. The dashboard mounts at the host root (`/`); there is no separate path-base prefix. |
| `MxGateway:Dashboard:AllowAnonymousLocalhost` | `true` | Allows loopback dashboard requests to bypass the dashboard cookie requirement for local development. Remote requests still require dashboard authentication. |
| `MxGateway:Dashboard:RequireHttpsCookie` | `true` | Sets the dashboard auth cookie's secure policy. `true` keeps `CookieSecurePolicy.Always` — the cookie is only sent over HTTPS, which matches a production HTTPS deployment. Set to `false` for plain-HTTP dev deployments to use `CookieSecurePolicy.SameAsRequest`; the cookie is still flagged Secure on HTTPS requests, but it can round-trip over HTTP. Browsers drop Secure cookies set over HTTP from non-localhost hosts, so leaving this `true` while serving the dashboard over plain HTTP will break login from any remote browser. |
+| `MxGateway:Dashboard:CookieName` | `MxGatewayDashboard` | Dashboard auth cookie name. Leave unset (null/blank) to use the default. Override it to give a distinct name to a gateway that shares a hostname with another gateway instance: browser cookies are scoped by host+path but **not** by port, so two instances on the same host would otherwise clobber each other's dashboard session under a shared cookie name. Changing it signs out existing dashboard sessions on next deploy. |
| `MxGateway:Dashboard:SnapshotIntervalMilliseconds` | `1000` | Dashboard snapshot refresh interval used by the snapshot SignalR hub and the pages that subscribe to it. |
| `MxGateway:Dashboard:RecentFaultLimit` | `100` | Maximum number of fault summaries projected into each dashboard snapshot. |
| `MxGateway:Dashboard:RecentSessionLimit` | `200` | Maximum number of session summaries projected into each dashboard snapshot. |
diff --git a/src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs b/src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs
index 7a56ef6..3b38ea1 100644
--- a/src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs
+++ b/src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs
@@ -21,6 +21,17 @@ public sealed class DashboardOptions
///
public bool RequireHttpsCookie { get; init; } = true;
+ ///
+ /// Dashboard auth cookie name. When null/blank (the default) the canonical
+ ///
+ /// is used. Override it (MxGateway:Dashboard:CookieName) to give a distinct name to a
+ /// gateway that shares a hostname with another gateway instance — browser cookies are scoped
+ /// by host+path but NOT by port, so two instances on the same host would otherwise clobber
+ /// each other's dashboard session under a shared cookie name. Changing this signs out
+ /// existing dashboard sessions on next deploy.
+ ///
+ public string? CookieName { get; init; }
+
/// Gets the dashboard snapshot update interval in milliseconds.
public int SnapshotIntervalMilliseconds { get; init; } = 1_000;
diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs
index 806fe41..b87708b 100644
--- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs
+++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs
@@ -66,6 +66,8 @@ public static class DashboardServiceCollectionExtensions
ZbCookieDefaults.Apply(cookieOptions, requireHttps: true, idleTimeout: TimeSpan.FromHours(8));
// Cookie name, path, and redirect paths are MxGateway-specific — set after Apply
// so they are never overwritten by the shared helper (Apply intentionally skips name).
+ // This is the canonical default; it is overridden per-environment from
+ // DashboardOptions.CookieName by the PostConfigure below.
cookieOptions.Cookie.Name = DashboardAuthenticationDefaults.CookieName;
cookieOptions.Cookie.Path = "/";
cookieOptions.LoginPath = "/login";
@@ -77,13 +79,22 @@ public static class DashboardServiceCollectionExtensions
_ => { });
// Honour DashboardOptions.RequireHttpsCookie (default true / Always; set false for dev
- // HTTP deployments → SameAsRequest). This overrides the Apply default above.
+ // HTTP deployments → SameAsRequest) and the optional per-environment cookie-name
+ // override. Both run after the inline AddCookie config above, so they win.
services.AddOptions(DashboardAuthenticationDefaults.AuthenticationScheme)
.Configure>((cookieOptions, gatewayOptions) =>
{
cookieOptions.Cookie.SecurePolicy = gatewayOptions.Value.Dashboard.RequireHttpsCookie
? CookieSecurePolicy.Always
: CookieSecurePolicy.SameAsRequest;
+
+ // Config-driven cookie name (MxGateway:Dashboard:CookieName). Null/blank keeps
+ // the canonical default set above, so a misconfiguration cannot unname the cookie.
+ var cookieName = gatewayOptions.Value.Dashboard.CookieName;
+ if (!string.IsNullOrWhiteSpace(cookieName))
+ {
+ cookieOptions.Cookie.Name = cookieName;
+ }
});
services.AddAuthorization(authorization =>
diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardCookieOptionsTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardCookieOptionsTests.cs
index 95ed26c..40fcfea 100644
--- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardCookieOptionsTests.cs
+++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardCookieOptionsTests.cs
@@ -49,4 +49,23 @@ public sealed class DashboardCookieOptionsTests
Assert.Equal(CookieSecurePolicy.SameAsRequest, options.Cookie.SecurePolicy);
}
+
+ ///
+ /// Verifies that MxGateway:Dashboard:CookieName overrides the dashboard auth
+ /// cookie name, so a gateway instance sharing a hostname with another can be given a
+ /// distinct name (browser cookies are scoped by host+path, not port).
+ ///
+ [Fact]
+ public async Task Build_WithCookieNameOverride_UsesConfiguredName()
+ {
+ await using WebApplication app = GatewayApplication.Build(
+ ["--MxGateway:Dashboard:CookieName=MxGatewayDashboard.env2"]);
+ IOptionsMonitor optionsMonitor = app.Services
+ .GetRequiredService>();
+
+ CookieAuthenticationOptions options = optionsMonitor.Get(
+ DashboardAuthenticationDefaults.AuthenticationScheme);
+
+ Assert.Equal("MxGatewayDashboard.env2", options.Cookie.Name);
+ }
}