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:
- Not working at all...
"Try it out" not working because:
- HTTP/HTTPS not being picked up
- Document being cached by different host or scheme meaning the opposite would not work
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;
}
}