diff --git a/src/OpenApi/src/Services/OpenApiDocumentService.cs b/src/OpenApi/src/Services/OpenApiDocumentService.cs index c8775e6b8e24..12452701d2d0 100644 --- a/src/OpenApi/src/Services/OpenApiDocumentService.cs +++ b/src/OpenApi/src/Services/OpenApiDocumentService.cs @@ -205,7 +205,18 @@ internal List GetOpenApiServers(HttpRequest? httpRequest = null) { if (httpRequest is not null) { - var serverUrl = UriHelper.BuildAbsolute(httpRequest.Scheme, httpRequest.Host, httpRequest.PathBase); + // Handle forwarded headers directly if present + var scheme = httpRequest.Headers.GetCommaSeparatedValues("X-Forwarded-Proto") is [var forwardedScheme, ..] + ? forwardedScheme + : httpRequest.Scheme; + + var host = httpRequest.Headers.GetCommaSeparatedValues("X-Forwarded-Host") is [var forwardedHost, ..] + ? forwardedHost + : httpRequest.Host.Value; + + var hostString = new HostString(host); + var serverUrl = UriHelper.BuildAbsolute(scheme, hostString, httpRequest.PathBase); + return [new OpenApiServer { Url = serverUrl }]; } else @@ -213,6 +224,7 @@ internal List GetOpenApiServers(HttpRequest? httpRequest = null) return GetDevelopmentOpenApiServers(); } } + private List GetDevelopmentOpenApiServers() { if (hostEnvironment.IsDevelopment() && diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Servers.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Servers.cs index 1bc247c95ad4..40286a8e1c88 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Servers.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Servers.cs @@ -146,4 +146,47 @@ public void GetOpenApiServers_HandlesServerAddressFeatureWithNoValues() // Assert Assert.Empty(servers); } + + [Theory] + [InlineData("https", "proxy-server.com", "https://proxy-server.com/original-path")] + [InlineData("http", "proxy:8080", "http://proxy:8080/original-path")] + [InlineData("https", "proxy.example.org", "https://proxy.example.org/original-path")] + public void GetOpenApiServers_HandlesForwardedHeaders(string forwardedProto, string forwardedHost, string expectedUrl) + { + // Arrange + var hostEnvironment = new HostingEnvironment + { + ApplicationName = "TestApplication", + EnvironmentName = "Production" + }; + var docService = new OpenApiDocumentService( + "v1", + new Mock().Object, + hostEnvironment, + GetMockOptionsMonitor(), + new Mock().Object, + new OpenApiTestServer(["http://localhost:5000"])); + + var httpContext = new DefaultHttpContext() + { + Request = + { + // Original values that should be overridden by forwarded headers + Host = new HostString("localhost:5000"), + PathBase = "/original-path", + Scheme = "http" + } + }; + + // Add forwarded headers + httpContext.Request.Headers["X-Forwarded-Proto"] = forwardedProto; + httpContext.Request.Headers["X-Forwarded-Host"] = forwardedHost; + + // Act + var servers = docService.GetOpenApiServers(httpContext.Request); + + // Assert + var serverUrl = Assert.Single(servers).Url; + Assert.Equal(expectedUrl, serverUrl); + } }