diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs index f214778..b1e02f3 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs @@ -44,6 +44,15 @@ public static class DashboardEndpointRouteBuilderExtensions .AllowAnonymous() .WithName("DashboardLogout"); + // Browsers that navigate directly to /logout issue a GET. Sign out and + // redirect to /login so the URL works as users expect. Logout is + // self-destructive, so the GET path intentionally skips antiforgery. + endpoints.MapGet( + "/logout", + (Delegate)(static (HttpContext httpContext) => GetLogoutAsync(httpContext))) + .AllowAnonymous() + .WithName("DashboardLogoutGet"); + endpoints.MapGet("/denied", () => Results.Content( RenderPage("Access denied", "

The signed-in user is not authorized for dashboard access.

"), "text/html")) @@ -140,6 +149,15 @@ public static class DashboardEndpointRouteBuilderExtensions return Results.LocalRedirect("/login"); } + private static async Task GetLogoutAsync(HttpContext httpContext) + { + await httpContext + .SignOutAsync(DashboardAuthenticationDefaults.AuthenticationScheme) + .ConfigureAwait(false); + + return Results.LocalRedirect("/login"); + } + private static string RenderLoginPage( HttpContext httpContext, IAntiforgery antiforgery, diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs index dbc226f..c590409 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs @@ -63,7 +63,7 @@ public sealed class GatewayApplicationTests IReadOnlyList endpoints = GetRouteEndpoints(app); string[] anonymousEndpointNames = - ["DashboardLogin", "DashboardLoginPost", "DashboardLogout", "DashboardAccessDenied"]; + ["DashboardLogin", "DashboardLoginPost", "DashboardLogout", "DashboardLogoutGet", "DashboardAccessDenied"]; foreach (string endpointName in anonymousEndpointNames) { RouteEndpoint endpoint = Assert.Single(