diff --git a/docker/central-node-a/appsettings.Central.json b/docker/central-node-a/appsettings.Central.json index 655dd80..cf41044 100644 --- a/docker/central-node-a/appsettings.Central.json +++ b/docker/central-node-a/appsettings.Central.json @@ -30,7 +30,8 @@ "LdapServiceAccountPassword": "password", "JwtSigningKey": "scadalink-dev-jwt-signing-key-must-be-at-least-32-characters-long", "JwtExpiryMinutes": 15, - "IdleTimeoutMinutes": 30 + "IdleTimeoutMinutes": 30, + "RequireHttpsCookie": false }, "Communication": { "DeploymentTimeout": "00:02:00", diff --git a/docker/central-node-b/appsettings.Central.json b/docker/central-node-b/appsettings.Central.json index adc6f12..47bb0c3 100644 --- a/docker/central-node-b/appsettings.Central.json +++ b/docker/central-node-b/appsettings.Central.json @@ -30,7 +30,8 @@ "LdapServiceAccountPassword": "password", "JwtSigningKey": "scadalink-dev-jwt-signing-key-must-be-at-least-32-characters-long", "JwtExpiryMinutes": 15, - "IdleTimeoutMinutes": 30 + "IdleTimeoutMinutes": 30, + "RequireHttpsCookie": false }, "Communication": { "DeploymentTimeout": "00:02:00", diff --git a/src/ScadaLink.Security/SecurityOptions.cs b/src/ScadaLink.Security/SecurityOptions.cs index 2b01579..d97ed31 100644 --- a/src/ScadaLink.Security/SecurityOptions.cs +++ b/src/ScadaLink.Security/SecurityOptions.cs @@ -92,4 +92,14 @@ public class SecurityOptions /// Minutes before token expiry to trigger refresh. /// public int JwtRefreshThresholdMinutes { get; set; } = 5; + + /// + /// When true (default) the authentication cookie is always marked + /// Secure (sent only over HTTPS) — the correct production setting, + /// since the cookie carries the embedded JWT bearer credential. Set false + /// for an HTTP-only deployment such as the local Docker dev cluster: the + /// cookie then uses SameAsRequest, so it is still Secure on + /// any HTTPS request but is usable over plain HTTP. + /// + public bool RequireHttpsCookie { get; set; } = true; } diff --git a/src/ScadaLink.Security/ServiceCollectionExtensions.cs b/src/ScadaLink.Security/ServiceCollectionExtensions.cs index 954feba..4cd3e0b 100644 --- a/src/ScadaLink.Security/ServiceCollectionExtensions.cs +++ b/src/ScadaLink.Security/ServiceCollectionExtensions.cs @@ -21,9 +21,8 @@ public static class ServiceCollectionExtensions options.Cookie.Name = "ScadaLink.Auth"; options.Cookie.HttpOnly = true; options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict; - // The cookie carries the embedded JWT (a bearer credential); never - // transmit it over plain HTTP. Design: "HttpOnly and Secure (requires HTTPS)". - options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always; + // Cookie.SecurePolicy is set in the PostConfigure block below so it + // can honour SecurityOptions.RequireHttpsCookie. }); // CentralUI-005: configure the cookie session as a sliding window so the @@ -46,6 +45,14 @@ public static class ServiceCollectionExtensions var idleMinutes = securityOptions.Value.IdleTimeoutMinutes; cookieOptions.ExpireTimeSpan = TimeSpan.FromMinutes(idleMinutes); cookieOptions.SlidingExpiration = true; + + // The cookie carries the embedded JWT bearer credential. Production + // keeps it HTTPS-only (Always); an HTTP-only deployment (e.g. the + // local Docker dev cluster) opts out via RequireHttpsCookie=false and + // uses SameAsRequest — still Secure on any HTTPS request. + cookieOptions.Cookie.SecurePolicy = securityOptions.Value.RequireHttpsCookie + ? Microsoft.AspNetCore.Http.CookieSecurePolicy.Always + : Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest; }); services.AddScadaLinkAuthorization();