Skip to content

Expose method for testing for a local URL consumable without depending on MVC #56770

Closed
@martincostello

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:

if (AcceptLocalUrlOnly && !isLocalUrl)
{
throw new InvalidOperationException("The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local.");
}

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:

  1. Bring in MVC to use IUrlHelper.IsLocalUrl();
  2. Copy the implementation of SharedUrlHelper.IsLocalUrl();
  3. 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.

Metadata

Assignees

No one assigned

    Labels

    NativeAOTapi-approvedAPI was approved in API review, it can be implementedarea-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etc

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions