1

How can I generate absolute links to other resources in my RESTful API app when the app is meant to be accessed via a reverse proxy that publishes just the paths under /api?

My app is an API with a common layout of routes like /api, /swagger and /health. It is published on my employer's API management under a path of the form /business-area/api-name/v1. Calling the API both directly and through the API gateway overall works: calling https://api-gateway.company.com/business-area/api-name/v1/some-resource results in internal call to https://my-app.company.com/api/some-resource.

The issue is that the links in my app's responses point directly to the backend app (https://my-app.company.com/api/another-resource), not the the API gateway (https://api-gateway.company.com/business-area/api-name/v1/another-resource). They are generated using IUrlHelper.

I solved the domain by the ForwardedHeadersMiddleware and adding the X-Forwarded-Host by a policy on the API management. Sadly, we are allowed to use just extremely simple policies, so if we published the API using multiple gateways, the current solution would generate link to just a single one. But that is an issue to be solved somewhen later; now it works OK.

I could not get the path to work well. I tried changing the paths using a middleware as hinted in the ASP.NET Core behind proxy docs:

app.Use((context, next) =>
{
    context.Request.PathBase = "/business-area/api-name/v1";

    if (context.Request.Path.StartsWithSegments("/api", out var remainder))
    {
        context.Request.Path = remainder;
    }

    return next();
});

When I insert this middleware high in the pipeline, it breaks the routing, but if I insert it low enough, the routing works OK and only link generation is affected. But it seems that only PathBase change really affects link generation as the /api is still in the generated URI. I can see that the Path of the request object is really changed, though, so it is probably just that link generation uses the routing info directly, without passing through my middleware, which makes sense, but it rules out the middleware solution.

Is wrapping the standard IUrlHelper in my own implementation and postprocessing the URLs it returns a good way to go? I don't know how to go about that. I use the IUrlHelper from the ControllerBase.Url property and debugger tells it is actually an instance of Microsoft.AspNetCore.Mvc.Routing.EndpointRoutingUrlHelper. Doing the wrapping in every action seems wrong (repetitive, error-prone).

Changing the routing so that /api moves to the root is my last resort option as it mixes up the namespaces: technical endpoints like /health and /swagger would live among the actual resources of the API. Is there a reasonable way to avoid that while keeping the links working? This all seems like a pretty standard problem and I am surprised I cannot find how to solve it.

We use .NET 5 and we will migrate to .NET 6 as soon as it is out, if that makes any difference.

Palec
  • 12,743
  • 8
  • 69
  • 138
  • Replace the `LinkGenerator` service? (based on https://github.com/dotnet/aspnetcore/blob/main/src/Http/Routing/src/DefaultLinkGenerator.cs) – Jeremy Lakeman Oct 26 '21 at 02:50
  • I am using IUrlHelper, not LinkGenerator, but the change would be quite a minor one. Both the URL helper and link generator seem quite complex to be just reimplemented, but maybe my custom ProxyLinkGenerator wrapping the actual LinkGenerator (registered as DefaultLinkGenerator in RoutingServiceCollectionExtensions) and [replacing the LinkGenerator registration](https://stackoverflow.com/a/43591075/2157640) would work. Seems there are just 4 methods to implement in the wrapper. https://github.com/dotnet/aspnetcore/blob/main/src/Http/Routing.Abstractions/src/LinkGenerator.cs – Palec Oct 26 '21 at 13:14
  • `IUrlHelper` used to be responsible for urls, now everything is a wrapper / extension around `LinkGenerator`. – Jeremy Lakeman Oct 26 '21 at 23:53

0 Answers0