Swashbuckle.aspnetcore: 6.0.7 SwaggerGen - Servers ignoring Forwarding Options

Created on 25 Feb 2021  路  13Comments  路  Source: domaindrivendev/Swashbuckle.AspNetCore

I have a HTTP service behind HTTPS terminated on balancer.

With v5.6.3 the swagger.json generates completely without the servers section and SwaggerUI calls the service via HTTPS no problem. No forwarding settings are needed anywhere in AspNetCore.

Once I upgrade to 6.0.7 the swagger.json file contains a server section and I found no way to remove it. It references to HTTP URL which is wrong according to headers.
I'm not sure if the servers section is now required by SwaggerUI, but if not, there should be an option to opt out and remove it.

I tried many ways to make it generate the correct URL like this, but everything was ignored:

var forwardingOptions = new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedProto
};
forwardingOptions.KnownNetworks.Clear();
forwardingOptions.KnownProxies.Clear();
app.UseForwardedHeaders(forwardingOptions);

Once I downgrade to 5.6.3, everything starts working again.

This is swagger.json generated by 5.6.3:

{
  "openapi": "3.0.1",
  "info": {
    "title": "MyService",
    "version": "1.0"
  },
  "paths": {

This is swagger.json generated by 6.0.7:

{
  "openapi": "3.0.1",
  "info": {
    "title": "MyService",
    "version": "1.0"
  },
  "servers": [
    {
      "url": "http://my-service-url.domain.com"
    }
  ],
  "paths": {

Any ideas?

Most helpful comment

Now, with the benefit of hindsight, I'm starting to sense it was a mistake to auto-populate the servers section, as leaving it empty appears to yield the best chance (there's def cases where it doesn't) of SB "just working" in proxy environments out-of-the-box. I'm thinking of reverting this and instead offering the functionality as an _opt-in_ flag. The question is, should this be a patch, minor version, or major version increment?

All 13 comments

Did you follow the exact steps listed here?
https://github.com/domaindrivendev/Swashbuckle.AspNetCore#working-with-reverse-proxies-and-load-balancers

If so, lets keep the issue open to get to the bottom of why Forwarded headers aren't working as they should. But, in the meantime if you want to omit the servers section altogether I would suggest the following:

app.UseSwagger(c =>
{
    c.PreSerializeFilters.Add((doc, req) => doc.Servers.Clear());
});

Thanks, clearing the Servers like this led to the 5.x.x behavior and everything magically just started working.

I read through that document, even tried the bit

app.Use((context, next) =>
{
    if (context.Request.Headers.TryGetValue("X-Forwarded-Prefix", out var value))
        context.Request.PathBase = value.First();

    return next();
});
app.UseSwagger();

But nothing changed the generated URL in swagger.json. The non-relative path from SwaggerUI would perhaps mask the problem but the swagger.json file generation is key for partners using the service definition and that URL would still point to a closed port.
Removing the section is an acceptable workaround since that's how everything worked up to this point.

I also have problems with version 6.0.7. I think it may be related to this issue. It was working fine with version 6.0.6. It looks like the PreSerializeFilters never gets executed.

With version 6.0.6 I can put a breakpoint this line var serverUrl = Environment.GetEnvironmentVariable("SWAGGER_SERVER_URL"); and it stops when I request the swagger page.

 app.UseSwagger(c =>
{
    c.PreSerializeFilters.Add((swagger, httpReq) =>
    {
        var serverUrl = Environment.GetEnvironmentVariable("SWAGGER_SERVER_URL");

        if (string.IsNullOrWhiteSpace(serverUrl)) 
        {
            serverUrl = $"{httpReq.Scheme}://{httpReq.Host.Value}";
        }

        swagger.Servers = new List<OpenApiServer> { new OpenApiServer { Url = serverUrl } };
    });
});

When I upgrade the nugets to 6.0.7 the breakpoint never stops the execution.

Maybe it is related to this change: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/commit/d88a5df146f3c2460feadb3c3abed979b353f5ff#diff-8d8a10887020c48dde7ed02036a888167000b2602bbf52995ed525f851d4d744

So when swagger is behind a reverse proxy, the URL of the server is wrong. If I run on localhost, the URL is reachable, but I can't change it or removing it with this order filter.

app.UseSwagger(c =>
{
    c.PreSerializeFilters.Add((doc, req) => doc.Servers.Clear());
});

Hope it make sens. :)

I figure out how to get to work, but I had to change my code like this :
```
app.UseSwagger();

app.UseEndpoints(endpoints =>
{
...
endpoints.MapSwagger("api/{documentName}/swagger.json", o =>
{
o.PreSerializeFilters.Add((swagger, httpReq) =>
{
var serverUrl = Environment.GetEnvironmentVariable("SWAGGER_SERVER_URL");

        if (string.IsNullOrWhiteSpace(serverUrl)) 
        {
            serverUrl = $"{httpReq.Scheme}://{httpReq.Host.Value}";
        }

        swagger.Servers = new List<OpenApiServer> { new OpenApiServer { Url = serverUrl } };
    });
});

});
````

Having the same issue as well.

Same problem here. Because of the added server section, the wrong URL is invoked.

Now, with the benefit of hindsight, I'm starting to sense it was a mistake to auto-populate the servers section, as leaving it empty appears to yield the best chance (there's def cases where it doesn't) of SB "just working" in proxy environments out-of-the-box. I'm thinking of reverting this and instead offering the functionality as an _opt-in_ flag. The question is, should this be a patch, minor version, or major version increment?

I would stick with minor and treat it as a bug. The thing is, even if people already started to do some voodoo to work around this problem (like populating the server list manually from configuration or clearing the list), this wouldn't be a breaking change per se, just a heap of unnecessary code.

Experience feedback: since Swashbuckle v6 generates absolute URL instead of relative ones, this is a breaking change with apps with reverse proxy because they have to adapt their code anyway. These apps typically already had to customize "servers" field with PreSerializeFilters (typically with adequate reverse proxy header(s), depending on use case), but this breaks with new behavior of Swashbuckle

Also, "servers" allows to set relative URLs, so this was easy to generate for kind of headers "X-Forwarded-Prefix" or "X-Forwarded-PathBase", without having to rely on other headers "X-Forwarded-Host" and the like. This was simpler IMHO.

Also: before, Swashbuckle would redirect from /swagger endpoint to swagger/index.html with relative location redirection. Now, this is an absolute URL and this breaks behind a reverse proxy (while it worked fine before, without any customization app side).

Because anyway there is nothing widely standard for reverse proxy headers, the way Swashbuckle previously worked was better because most of time it worked "as is", and app behind reverse proxy had just a few customization to do depending on their specific (with new behavior of Swashbuckle, they may have to do custo for "servers" field, but they are not the same custo now, so it mostly complicates things).

I've just reeased 6.1.0, which reverts back to the old behavior (i.e. leaving the servers section empty and returning _relative_ redirects from the SwaggerUI and ReDoc middleware) as this is a far more friendly default for apps running behind proxies. The one
remaining gotcha for proxy environments is the URL(s) passed to the SwaggerEndpoint method when configuring the SwaggerUI middleware - as this is from the perspective of code running in the browser, it should be relative to the swagger-ui page itself. I've provided the following section in the docs to call this out:

https://github.com/domaindrivendev/Swashbuckle.AspNetCore#working-with-virtual-directories-and-reverse-proxies

@niker @eric-b could you guys please pull down this version and ensure these changes work for you?

I just tried to remove the server section hack and 6.1.0 behaves exactly like 5.x.x in that regard, no issues that I can see.
Thank you!

Thanks, at first glance, it works like it was before.
I only run my previous unit tests. I'll check it behind actual reverse proxy tomorrow to see if our previous customization for "servers" still works.

I can confirm this works as before, like a charm behind a reverse proxy, with adequate PreSerializeFilter.

:ok_hand:

Was this page helpful?
0 / 5 - 0 ratings