Expose method for testing for a local URL consumable without depending on MVC #56770
Description
Background and Motivation
I have an endpoint in an Minimal API application using Razor Slices so that native AoT could be supported whilst having a web UI that has an endpoint for handling rendering a sign in page similar to this:
builder.MapGet("/sign-in", (HttpContext context) =>
{
if (context.User.Identity?.IsAuthenticated == true)
{
string url = "/";
if (context.Request.Query.TryGetValue("ReturnUrl", out var returnUrl))
{
url = returnUrl.ToString();
}
return Results.LocalRedirect(url);
}
return Results.Extensions.RazorSlice<Slices.SignIn>();
});
This protects against open redirects via ?ReturnUrl={value}
using Results.LocalRedirect()
, but in the case that a non-local URL is provided, this causes an HTTP 500 due to these lines of code:
aspnetcore/src/Http/Http.Results/src/RedirectHttpResult.cs
Lines 99 to 102 in 90a622d
IUrlHelper.IsLocal()
isn't usable here because it's part of MVC which I don't have otherwise added to the application.
To get the desired behaviour of "use ReturnUrl
if local, otherwise just ignore it and go to the homepage" I'm left with a few options including:
- Bring in MVC to use
IUrlHelper.IsLocalUrl()
; - Copy the implementation of
SharedUrlHelper.IsLocalUrl()
; - Use reflection to invoke
SharedUrlHelper.IsLocalUrl()
.
This feels like a "missing piece" of functionality for general utility of lightweight Minimal APIs where MVC isn't a desired dependency of the application.
Proposed API
namespace Microsoft.AspNetCore.Http;
+public static class UrlHelper
+{
+ public static bool IsLocalUrl(string? url) => SharedUrlHelper.IsLocalUrl(url);
+}
Usage Examples
builder.MapGet("/sign-in", (HttpContext context) =>
{
if (context.User.Identity?.IsAuthenticated == true)
{
string url = "/";
if (context.Request.Query.TryGetValue("ReturnUrl", out var returnUrl))
{
var maybeLocal = returnUrl.ToString();
if (UrlHelper.IsLocalUrl(maybeLocal))
{
url = maybeLocal;
}
}
return Results.LocalRedirect(url);
}
return Results.Extensions.RazorSlice<Slices.SignIn>();
});
Alternative Designs
namespace Microsoft.AspNetCore.Http;
public sealed partial class RedirectHttpResult
{
+ public static bool IsLocalUrl(string? url) => SharedUrlHelper.IsLocalUrl(url);
}
Or, a new abstraction similar to IUrlHelper
that is uncoupled from MVC, but then there's lots more open questions about what other functionality should or shouldn't be in it.
Risks
Duplication of class name with Microsoft.AspNetCore.Mvc.Routing.UrlHelper
.