Dashboard: handle GET /logout (was 405) by signing out + redirecting to /login
Browsers that navigate directly to /logout via the address bar issued a GET against a POST-only route and got 405 Method Not Allowed. Logout is self-destructive, so the GET path can skip antiforgery; the existing POST form (used by the layout's Sign out button) is unchanged and still antiforgery-protected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,15 @@ public static class DashboardEndpointRouteBuilderExtensions
|
|||||||
.AllowAnonymous()
|
.AllowAnonymous()
|
||||||
.WithName("DashboardLogout");
|
.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(
|
endpoints.MapGet("/denied", () => Results.Content(
|
||||||
RenderPage("Access denied", "<p>The signed-in user is not authorized for dashboard access.</p>"),
|
RenderPage("Access denied", "<p>The signed-in user is not authorized for dashboard access.</p>"),
|
||||||
"text/html"))
|
"text/html"))
|
||||||
@@ -140,6 +149,15 @@ public static class DashboardEndpointRouteBuilderExtensions
|
|||||||
return Results.LocalRedirect("/login");
|
return Results.LocalRedirect("/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> GetLogoutAsync(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
await httpContext
|
||||||
|
.SignOutAsync(DashboardAuthenticationDefaults.AuthenticationScheme)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return Results.LocalRedirect("/login");
|
||||||
|
}
|
||||||
|
|
||||||
private static string RenderLoginPage(
|
private static string RenderLoginPage(
|
||||||
HttpContext httpContext,
|
HttpContext httpContext,
|
||||||
IAntiforgery antiforgery,
|
IAntiforgery antiforgery,
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public sealed class GatewayApplicationTests
|
|||||||
IReadOnlyList<RouteEndpoint> endpoints = GetRouteEndpoints(app);
|
IReadOnlyList<RouteEndpoint> endpoints = GetRouteEndpoints(app);
|
||||||
|
|
||||||
string[] anonymousEndpointNames =
|
string[] anonymousEndpointNames =
|
||||||
["DashboardLogin", "DashboardLoginPost", "DashboardLogout", "DashboardAccessDenied"];
|
["DashboardLogin", "DashboardLoginPost", "DashboardLogout", "DashboardLogoutGet", "DashboardAccessDenied"];
|
||||||
foreach (string endpointName in anonymousEndpointNames)
|
foreach (string endpointName in anonymousEndpointNames)
|
||||||
{
|
{
|
||||||
RouteEndpoint endpoint = Assert.Single(
|
RouteEndpoint endpoint = Assert.Single(
|
||||||
|
|||||||
Reference in New Issue
Block a user