Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
martincostello opened this issue Jul 13, 2024 · 3 comments · Fixed by #57363
Closed

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

martincostello opened this issue Jul 13, 2024 · 3 comments · Fixed by #57363
Labels
api-approved API was approved in API review, it can be implemented area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc NativeAOT
Milestone

Comments

@martincostello
Copy link
Member

martincostello commented Jul 13, 2024

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.

@martincostello martincostello added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Jul 13, 2024
@ghost ghost added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label Jul 13, 2024
@martincostello martincostello added old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels and removed area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates labels Jul 14, 2024
@captainsafia captainsafia added api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Aug 9, 2024
Copy link
Contributor

Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:

  • The PR contains changes to the reference-assembly that describe the API change. Or, you have included a snippet of reference-assembly-style code that illustrates the API change.
  • The PR describes the impact to users, both positive (useful new APIs) and negative (breaking changes).
  • Someone is assigned to "champion" this change in the meeting, and they understand the impact and design of the change.

@captainsafia
Copy link
Member

Filing this for API review. I personally like the style of exposing a static method on the RedirectResult class but I'll leave that to discuss in API review.

Parking in the Backlog for now until we review and figure out when it gets conclusion.

@captainsafia captainsafia added this to the Backlog milestone Aug 9, 2024
@captainsafia captainsafia added area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc and removed old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels labels Aug 14, 2024
@amcasey
Copy link
Member

amcasey commented Aug 15, 2024

[API Review]

  1. Do we want to expose this?
  2. Where do we want to put it?
  3. RedirectHttpResult?
  4. A new static helper type?
  • This would have been useful here
  • Concern: "local url" isn't standard terminology and might be misunderstood
  • MVC exposes it via DI, possibly so that it can be overridden
  • The argument against reimplementation is that the user's version could end up being inconsistent with ours, causing problems
  • We don't have a lot of precedent for a static utility class
    • Putting it on RedirectHttpResult might be a good compromise
namespace Microsoft.AspNetCore.Http;

public sealed partial class RedirectHttpResult
{
+   public static bool IsLocalUrl(string? url) => SharedUrlHelper.IsLocalUrl(url);
}

API approved!

@amcasey amcasey added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews labels Aug 15, 2024
martincostello added a commit to martincostello/aspnetcore that referenced this issue Aug 16, 2024
Add `IsLocalUrl()` method to `RedirectHttpResult`, which is just a wrapper around `SharedUrlHelper.IsLocalUrl()`.

Resolves dotnet#56770.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-approved API was approved in API review, it can be implemented area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc NativeAOT
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants