|
4 | 4 | using System.CommandLine.Parsing; |
5 | 5 | using System.Diagnostics; |
6 | 6 | using System.Net; |
| 7 | +using Azure.Core; |
7 | 8 | using Azure.Mcp.Core.Areas.Server.Models; |
8 | 9 | using Azure.Mcp.Core.Areas.Server.Options; |
9 | 10 | using Azure.Mcp.Core.Commands; |
|
21 | 22 | using Microsoft.Extensions.Hosting; |
22 | 23 | using Microsoft.Extensions.Logging; |
23 | 24 | using Microsoft.Extensions.Options; |
| 25 | +using Microsoft.Extensions.Primitives; |
24 | 26 | using Microsoft.Identity.Web; |
25 | 27 | using OpenTelemetry; |
26 | 28 | using OpenTelemetry.Logs; |
@@ -414,7 +416,8 @@ private IHost CreateHttpHost(ServiceStartOptions serverOptions) |
414 | 416 | if (!context.Response.HasStarted) |
415 | 417 | { |
416 | 418 | HttpRequest request = context.Request; |
417 | | - string resourceMetadataUrl = $"{request.Scheme}://{request.Host}/.well-known/oauth-protected-resource"; |
| 419 | + string scheme = GetSchemeForOAuthProtectedResourceMetadata(request); |
| 420 | + string resourceMetadataUrl = $"{scheme}://{request.Host}/.well-known/oauth-protected-resource"; |
418 | 421 |
|
419 | 422 | // Modify the WWW-Authenticate header to include resource_metadata |
420 | 423 | context.Response.Headers.WWWAuthenticate = |
@@ -498,7 +501,8 @@ private IHost CreateHttpHost(ServiceStartOptions serverOptions) |
498 | 501 | .GetRequiredService<IOptionsMonitor<MicrosoftIdentityOptions>>(); |
499 | 502 | MicrosoftIdentityOptions azureAdOptions = azureAdOptionsMonitor.Get(JwtBearerDefaults.AuthenticationScheme); |
500 | 503 | HttpRequest request = context.Request; |
501 | | - string baseUrl = $"{request.Scheme}://{request.Host}"; |
| 504 | + string scheme = GetSchemeForOAuthProtectedResourceMetadata(request); |
| 505 | + string baseUrl = $"{scheme}://{request.Host}"; |
502 | 506 | string? clientId = azureAdOptions.ClientId; |
503 | 507 | string? tenantId = azureAdOptions.TenantId; |
504 | 508 | string instance = azureAdOptions.Instance?.TrimEnd('/') ?? "https://login.microsoftonline.com"; |
@@ -552,6 +556,50 @@ await JsonSerializer.SerializeAsync( |
552 | 556 | .AllowAnonymous(); |
553 | 557 |
|
554 | 558 | return app; |
| 559 | + |
| 560 | + string GetSchemeForOAuthProtectedResourceMetadata(HttpRequest request) |
| 561 | + { |
| 562 | + string scheme = request.Scheme; |
| 563 | + |
| 564 | + // Default to "false" for enabling forwarded headers. The env var must be present, |
| 565 | + // and it must be parsed to "true". |
| 566 | + bool enableForwardedHeaders = |
| 567 | + bool.TryParse( |
| 568 | + Environment.GetEnvironmentVariable("AZURE_MCP_DANGEROUSLY_ENABLE_FORWARDED_HEADERS"), |
| 569 | + out bool parsedEnvVar) |
| 570 | + && parsedEnvVar; |
| 571 | + |
| 572 | + // Azure Container Apps setups usually use HTTP between the ACA platform's |
| 573 | + // reverse proxy and the application container. Our OAuth claims challenge |
| 574 | + // needs to match what the client will use as a scheme. So only in this |
| 575 | + // case do we use the X-Forwarded-Proto header if present. We're also going |
| 576 | + // to limit specifically to "http" and "https" values and use their |
| 577 | + // lowercase forms rather than the casing in the header. |
| 578 | + // |
| 579 | + // Other reverse proxies or load balancers may also use X-Forwarded-Proto or |
| 580 | + // may use something different. We only special case ACA here because it's |
| 581 | + // part of the samples as of 2.0-beta.5. More thorough logic and any |
| 582 | + // configuration options can be added later if needed, and that could use |
| 583 | + // ASP.NET Core's Forwarded Headers Middleware. See: |
| 584 | + // https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer |
| 585 | + if (enableForwardedHeaders |
| 586 | + && request.Headers.TryGetValue("X-Forwarded-Proto", out StringValues forwardedProto)) |
| 587 | + { |
| 588 | + if (forwardedProto.FirstOrDefault() is string forwardedProtoValue) |
| 589 | + { |
| 590 | + if (string.Equals(forwardedProtoValue, "https", StringComparison.OrdinalIgnoreCase)) |
| 591 | + { |
| 592 | + scheme = "https"; |
| 593 | + } |
| 594 | + else if (string.Equals(forwardedProtoValue, "http", StringComparison.OrdinalIgnoreCase)) |
| 595 | + { |
| 596 | + scheme = "http"; |
| 597 | + } |
| 598 | + } |
| 599 | + } |
| 600 | + |
| 601 | + return scheme; |
| 602 | + } |
555 | 603 | } |
556 | 604 |
|
557 | 605 | /// <summary> |
|
0 commit comments