fix(security): make auth-cookie SecurePolicy configurable for HTTP-only deployments

The cookie SecurePolicy was hard-coded to Always, so the auth cookie was always
marked Secure and the browser never sent it over plain HTTP — making login
impossible on the HTTP-only Docker dev cluster (login succeeded server-side but
every following request was unauthenticated). Add SecurityOptions.RequireHttps-
Cookie (default true — production stays HTTPS-only); when false the cookie uses
SameAsRequest. The docker/ central nodes set it false.
This commit is contained in:
Joseph Doherty
2026-05-18 02:34:02 -04:00
parent deedf45676
commit 579522c586
4 changed files with 24 additions and 5 deletions

View File

@@ -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",

View File

@@ -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",

View File

@@ -92,4 +92,14 @@ public class SecurityOptions
/// Minutes before token expiry to trigger refresh.
/// </summary>
public int JwtRefreshThresholdMinutes { get; set; } = 5;
/// <summary>
/// When true (default) the authentication cookie is always marked
/// <c>Secure</c> (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 <c>SameAsRequest</c>, so it is still <c>Secure</c> on
/// any HTTPS request but is usable over plain HTTP.
/// </summary>
public bool RequireHttpsCookie { get; set; } = true;
}

View File

@@ -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();