From df56185c6905370619587cedda6b81f2638c2e06 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 4 Sep 2026 20:11:48 -0400 Subject: [PATCH 1/2] feat(security): Protect management UI sessions Signed-off-by: Yordis Prieto --- docs/security.md | 23 ++ proto.lock | 20 ++ .../Components/Pages/Operations.razor | 4 + .../Components/Pages/ScavengeDetail.razor | 1 + .../Components/Pages/SignIn.razor | 17 +- .../Components/Pages/SignOut.razor | 26 +- .../Services/OAuthBrowserFlowEndpoints.cs | 22 +- .../Services/SecurityBrowserService.cs | 19 +- .../Components/Services/UiCredentialCookie.cs | 146 +---------- .../Services/UiCredentialsMiddleware.cs | 7 +- .../Services/UiSessionAuthentication.cs | 169 +++++++++++++ .../Services/UiSessionTicketStore.cs | 37 +++ .../Components/Shared/NodeCommandCard.razor | 1 + src/EventStore.ClusterNode/Program.cs | 1 + .../ui-assets/js/admin-operations.js | 9 +- .../ui-assets/js/ui-auth.js | 114 +-------- .../InternalSessionAuthenticationTests.cs | 200 +++++++++++++++ .../OAuthBrowserFlowServiceTests.cs | 160 +++++++++++- .../UiSessionIntegrationTests.cs | 237 ++++++++++++++++++ .../Authentication/UiSessionSecurityTests.cs | 31 +++ .../UiSessionTicketStoreTests.cs | 24 ++ .../Forwarding/ForwardingGrpcCodecTests.cs | 26 ++ .../Grpc/Forwarding/ForwardingServiceTests.cs | 83 ++++++ .../CompositeAuthenticationProvider.cs | 18 +- .../DelegatedAuthenticationProvider.cs | 16 +- .../ISessionAuthenticationProvider.cs | 12 + .../InternalAuthenticationProvider.cs | 91 +++++-- .../LocalSessionClaimsIdentity.cs | 6 + src/EventStore.Core/ClusterVNodeStartup.cs | 8 +- .../Grpc/Forwarding/ForwardingGrpcCodec.cs | 23 +- .../Grpc/Forwarding/ForwardingService.cs | 33 +++ .../Http/AuthenticationMiddleware.cs | 23 +- .../Transport/Http/IUiSessionAuthenticator.cs | 11 + src/Protos/Grpc/forwarding.proto | 6 + 34 files changed, 1311 insertions(+), 313 deletions(-) create mode 100644 src/EventStore.ClusterNode/Components/Services/UiSessionAuthentication.cs create mode 100644 src/EventStore.ClusterNode/Components/Services/UiSessionTicketStore.cs create mode 100644 src/EventStore.Core.Tests/Authentication/InternalSessionAuthenticationTests.cs create mode 100644 src/EventStore.Core.Tests/Authentication/UiSessionIntegrationTests.cs create mode 100644 src/EventStore.Core.Tests/Authentication/UiSessionSecurityTests.cs create mode 100644 src/EventStore.Core.Tests/Authentication/UiSessionTicketStoreTests.cs create mode 100644 src/EventStore.Core/Authentication/ISessionAuthenticationProvider.cs create mode 100644 src/EventStore.Core/Authentication/LocalSessionClaimsIdentity.cs create mode 100644 src/EventStore.Core/Services/Transport/Http/IUiSessionAuthenticator.cs diff --git a/docs/security.md b/docs/security.md index b9eb538865..4518f0fb42 100644 --- a/docs/security.md +++ b/docs/security.md @@ -527,6 +527,29 @@ making the database authentication method explicit, so password and OAuth access Authentication is applied to all HTTP endpoints by default, except `/-/liveness`, `/-/readiness`, static web content, and redirects. +### Management UI sessions + +Browser sign-in uses ASP.NET Core cookie authentication with a protected session identifier. Passwords are +used only when signing in; they are not retained in the browser or the session store. OAuth access tokens +remain in the node's memory and are validated on subsequent requests. Sessions expire after 15 minutes +without automatic renewal. Signing out revokes the server-side ticket, including copies of its cookie. +Existing credential cookies are discarded and require a new sign-in after upgrading. + +Browser sign-in requires HTTPS to the node, including when an ingress or reverse proxy terminates public +TLS. Configure TLS on the upstream connection as well. `DisableTls` does not enable browser sessions over +HTTP. Session cookies are Secure, HttpOnly, host-only, and SameSite=Lax. Cookie authentication is limited +to `/ui`; gRPC clients continue to supply their own credentials. UI mutations require antiforgery tokens. + +Sessions are held in a bounded, node-local memory store. A node restart, store eviction, or connection to +a different node requires signing in again. Use a node-specific management address or session affinity +at the ingress. Sharing Data Protection keys alone does not share the session store. + +Each password session is checked against the latest local user record on every request. Password changes, +role changes, disabling, or deleting the account invalidate its previous sessions as those changes replicate +to each node. A node that cannot validate the account rejects the session. Forwarded writes are revalidated +by the leader over the authenticated TLS connection between nodes. During a rolling upgrade, use the +leader's UI for writes until all nodes support session forwarding. + ### Authentication methods Use `Auth:Methods` to choose the authentication methods enabled by the node. If `Auth:Methods` is not set, the diff --git a/proto.lock b/proto.lock index f01a43a1fa..068485f54f 100644 --- a/proto.lock +++ b/proto.lock @@ -2343,6 +2343,26 @@ "id": 4, "name": "anonymous", "type": "google.protobuf.Empty" + }, + { + "id": 5, + "name": "local_session", + "type": "LocalSession" + } + ] + }, + { + "name": "LocalSession", + "fields": [ + { + "id": 1, + "name": "username", + "type": "string" + }, + { + "id": 2, + "name": "user_event_id", + "type": "event_store.client.UUID" } ] }, diff --git a/src/EventStore.ClusterNode/Components/Pages/Operations.razor b/src/EventStore.ClusterNode/Components/Pages/Operations.razor index 0c22341f3c..7f6fadbdbb 100644 --- a/src/EventStore.ClusterNode/Components/Pages/Operations.razor +++ b/src/EventStore.ClusterNode/Components/Pages/Operations.razor @@ -85,6 +85,7 @@ else }
+