Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swashbuckle using port 80 for https when run behind reverse proxy

I have a .net core api documented with swagger/swashbuckle.

When running the swagger ui on localhost on url https://localhost:44390/ the "Try it out" works fine.

We have the same solution in an App service in Azure with an Azure Front Door acting as reverse proxy. Front Door only accepts https traffic and only forwards https traffic. Front door domain is widget.example.com and App service is widget-test-app.azurewebsites.net. When running the swagger ui in Azure using the url https://widget.example.com/api/index.html there are two differences compared to running in localhost:

  1. The swagger ui is showing a Servers -heading and a dropdown
  2. The swagger ui is showing the server url as https://widget.example.com:80

I added an endpoint in the api with the following code

return $"Host {HttpContext.Request.Host.Host} Port {HttpContext.Request.Host.Port} Https {HttpContext.Request.IsHttps}";

When requesting https://widget.example.com/api/v1/test/url it returns

Host widget-test-app.azurewebsites.net Port Https True

This is completely ok since Front door is changing the host header. Port is empty, though.

Summary: Swagger ui is showing the correct domain in the Servers -dropdown but the port number is wrong. How can I get it to either omit the port number if it's 80 or 443, or add it correctly?

Update: The problem is in the swagger.json file which behind the reverse proxy includes a servers element

"servers": [{
  "url": "https://widget.example.com:80"
}]

Startup.ConfigureServices

services.AddApiVersioning(options => {
  options.Conventions.Add(new VersionByNamespaceConvention());
});

services.AddVersionedApiExplorer(o => {
  o.GroupNameFormat = "'v'VVV";
  o.SubstituteApiVersionInUrl = true;
});

services.AddSwaggerGen(c => {
  c.SwaggerDoc("v1", new OpenApiInfo {
    Title = "Widget backend v1", Version = "v1"
  });
  c.SwaggerDoc("v2", new OpenApiInfo {
    Title = "Widget backend v2", Version = "v2"
  });
  c.EnableAnnotations();
  c.AddEnumsWithValuesFixFilters();

  var xmlFile = $ "{Assembly.GetExecutingAssembly().GetName().Name}.xml";
  var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
  c.IncludeXmlComments(xmlPath);
});

Startup.Configure

app.UseSwagger(options => {
  options.RouteTemplate = "/api/swagger/{documentname}/swagger.json";
});

app.UseSwaggerUI(options => {
  foreach(var description in provider.ApiVersionDescriptions) {
    options.SwaggerEndpoint($ "/api/swagger/{description.GroupName}/swagger.json", "widget backend " + description.GroupName);
  }
  options.RoutePrefix = "api";
});
like image 542
Mathias Rönnlund Avatar asked Oct 18 '20 20:10

Mathias Rönnlund


2 Answers

To fix this I cleared the Servers -list. Here is my code:

app.UseSwagger(options =>
{
    options.RouteTemplate = "/api/swagger/{documentname}/swagger.json";
    options.PreSerializeFilters.Add((swagger, httpReq) =>
    {
        //Clear servers -element in swagger.json because it got the wrong port when hosted behind reverse proxy
        swagger.Servers.Clear();
    });
});
like image 51
Mathias Rönnlund Avatar answered Nov 18 '22 04:11

Mathias Rönnlund


The solution (ok, a - mine - solution :)) is to configure forward headers in Startup.

services.Configure<ForwardHeadersOptions>(options =>
{
    options.ForwardHeaders = ForwardHeaders.All; // For, Proto and Host
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();
});

Doing this, any URL generation in the app (behind reverse proxy) should respect the port-forwarding value. According to documentation known networks should be specified (taken from docs):

Only allow trusted proxies and networks to forward headers. Otherwise, IP spoofing attacks are possible.

See ASP.NET documentation for more details.

like image 1
joacar Avatar answered Nov 18 '22 04:11

joacar