Getting NSwag to work behind reverse proxy

Search for a command to run...

No comments yet. Be the first to comment.
Polly is great library for easily configuring different kinds of policies around code execution, be it retry, circuit breaker, caching or lots of other things. It's probably most often used around HTTP requests. If you haven't heard about it you defi...

This is part 2 of my series on unit testing in .NET (C#) and will be about my conventions regarding naming unit tests, how to structure them and which parts to test. And lastly I will help you increase readability of assertions and potential error me...

I feel like unit testing is very important and very beneficial in a lot of ways. It helps with not breaking existing functionality and thereby also making you more confident in changing code without fear of breaking important stuff. Often it also mak...

The other day I was writing a unit test to verify what was supposed to happen when a Task would throw an exception when awaited. It wasn't as straight forward as I first assumed, but I quickly found this Stack Overflow post that helped me: https://st...

I've been working with Swagger through NSwag a lot recently and also needed to get it to work when hidden behind a reverse proxy - i.e. another service forwarding a request to the service exposing the Swagger UI.
Some of the issues I've had along the way:
"Try it out" not working because:
It wasn't that straight forward but through a lot of googling and reading GitHub issues I got a working solution put together.
In my specific case I was running ASP.NET Core 3.1 in a linux docker container in Azure, which some of the solution might be colored by.
If I remember correctly the most important parts are the X-Forwarded-Host and X-Forwarded-PathBase HTTP headers, so it's important that these are forwarded from your proxy to your Swagger service.
public class Startup
{
// ...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
app.UseXForwardedHeaders();
app.UseSwaggerWithReverseProxySupport();
}
}
static class StartupExtensions
{
public static IApplicationBuilder UseXForwardedHeaders(this IApplicationBuilder app)
{
var options = new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
};
// Only loopback proxies are allowed by default.
// Clear that restriction because forwarders are being enabled by explicit configuration.
// https://stackoverflow.com/a/56469499/5358985
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
app.UseForwardedHeaders(options);
app.Use((context, next) =>
{
if (context.Request.Headers.TryGetValue("X-Forwarded-PathBase", out var pathBases))
{
context.Request.PathBase = pathBases.First();
}
return next();
});
return app;
}
public static IApplicationBuilder UseSwaggerWithReverseProxySupport(this IApplicationBuilder app)
{
app.UseOpenApi(config =>
{
// Without this the document will be cached with wrong URLs
// and not work if later accessed from another host/path/scheme
config.CreateDocumentCacheKey = request => request.Headers["X-Forwarded-Host"].FirstOrDefault() +
request.Headers["X-Forwarded-PathBase"].FirstOrDefault() +
request.IsHttps;
// Change document host and base path from headers (if set)
config.PostProcess = (document, request) =>
{
document.BasePath = request.Headers["X-Forwarded-PathBase"].FirstOrDefault();
document.Host = request.Headers["X-Forwarded-Host"].FirstOrDefault();
};
});
app.UseSwaggerUi3(settings =>
{
settings.TransformToExternalPath = (route, request) =>
{
var pathBase = request.Headers["X-Forwarded-PathBase"].FirstOrDefault();
return !string.IsNullOrEmpty(pathBase)
? $"{pathBase}{route}"
: route;
};
});
return app;
}
}