I'm really struggling to make swagger work when using it behind nginx. In my scenario nginx will listen on:
http://localhost:80/ => http://localhost:8080
http://localhost:80/bar => http://localhost:81
Such that a request going to http://localhost:80/bar/swagger/ will go to http:/localhost:81/swagger/
Now I have swagger enabled on http://localhost:81 but return addresses do not respect the original address. Which makes my browser redirect to http://localhost:8080. I need a way to specify that all the links should be prefixed with bar so that the browser is interacting with the correct server.
A bit of additional information. X-Forwarded-* are present and look something like:
X-Forwarded-For: 172.18.0.1
X-Forwarded-Proto: http
X-Forwarded-Host: localhost:80/bar/
Using the UseForwardedHeaders middleware the Request.Host property is correctly updated to reflect this. But it doesn't look like the swagger middleware is respecting that.
Using the following snippet UriHelper.BuildAbsolute(context.Request.Scheme, context.Request.Host) will give the correct path when UseForwardedHeaders is used as it replaces the host. So probably just needs a Path.Combine and that will give the correct return paths.
After investigating the source code and the UseForwardedHeaders middleware I have come to the conclusion that the problem is not as simple as one might think.
For starters, localhost:80/bar/ is not a valid value for X-Forwarded-Host, although there is not a specification for that header it is expected that it will only contain the host portion of the server that forwarded the request. You can find for more information about it here.
Taking that into consideration a possible workaround to generate the correct Swagger JSON is to send another header named X-Forwarded-Path with the path it is proxying, and setting the Request's PathBase to the that value in a middleware:
app.Use((context, next) =>
{
if (context.Request.Headers.TryGetValue("X-Forwarded-Path", out StringValues values)
&& values.Count > 0)
{
context.Request.PathBase = values[0];
}
return next();
});
To setup NGINX to send the custom header all you have to do is add the following line to your location configuration:
proxy_set_header X-Forwarded-Path /<your nginx location>/;
If you are not using Swagger UI this should be enough for you as you will be serving the Swagger JSON with the correct endpoints.
If you are using the Swagger UI you will need to set an additional Swagger endpoint when adding the SwaggerUi middleware, unfortunately this is not ideal since it is set at compile time, this is due to Swashbuckle using a static file middleware to serve the SwaggerUi html file.
To set the additional Swagger endpoint you use the method SwaggerEndpoint of the SwaggerUiOptions instance passed to the UseSwaggerUi action. E.g.:
app.UseSwaggerUi(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "NAME");
c.SwaggerEndpoint("/name/swagger/v1/swagger.json", "NAME Reverse proxy");
});
This will leave you with two options in the select box of the Swagger Ui upper right corner.
No workaround ?
The best way to deal with reverse proxies is to use relative URLs in your configuration.
app.UseSwaggerUi(c =>
{
c.SwaggerEndpoint("v1/swagger.json", "NAME");
}
NOTE the lack of a leading slash. This means that the requested path will be relative to the swagger-ui page itself. So, if you're serving the swagger-ui at example.com:80/bar/swagger, then it will request the Swagger JSON at example.com:80/bar/swagger/v1/swagger.json
Hi @domaindrivendev, thanks for reply, relative end point is the trick.
I am using a relative path but when I load Swagger UI and it tries to load the JSON, it seems to add an additional /swagger to the beginning of the JSON url so I end up with a path like
https://api-staging.mydomain.com/users/swagger/swagger/v1/swagger.json
if I navigate to
https://api-staging.mydomain.com/users/swagger
Swagger UI loads correctly. It's just the JSON that is wrong.
Admittedly I came across my fix by trial and error, but in the Configure method of Startup.cs I had to add use the following:
```c#
app.UseSwagger(c => {
//change the path to include /api
c.RouteTemplate = "/api/swagger/{documentName}/swagger.json";
});
app.UseSwaggerUI(c =>
{
//Notice the lack of / making it relative
c.SwaggerEndpoint("swagger/v1/swagger.json", "My API V1");
//This is the reverse proxy address
c.RoutePrefix = "api";
});
```
Obviously my reverse proxy forwards /api to this service. I had to adjust the RouteTemplate for the json file as well as add the RouteTemplate for the UI. If there is a better solution I'd love to see it. I have a bare minimum example at https://github.com/lukerogers/swashbuckle-nginx
Most helpful comment
The best way to deal with reverse proxies is to use relative URLs in your configuration.
NOTE the lack of a leading slash. This means that the requested path will be relative to the swagger-ui page itself. So, if you're serving the swagger-ui at
example.com:80/bar/swagger, then it will request the Swagger JSON atexample.com:80/bar/swagger/v1/swagger.json