Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal partial class DownstreamApi : IDownstreamApi
private readonly IOptionsMonitor<DownstreamApiOptions> _namedDownstreamApiOptions;
private const string Authorization = "Authorization";
protected readonly ILogger<DownstreamApi> _logger;
private const string AuthSchemeDstsSamlBearer = "http://schemas.microsoft.com/dsts/saml2-bearer";

/// <summary>
/// Constructor.
Expand Down Expand Up @@ -522,7 +523,15 @@ public Task<HttpResponseMessage> CallApiForAppAsync(
user,
cancellationToken).ConfigureAwait(false);

httpRequestMessage.Headers.Add(Authorization, authorizationHeader);
if (authorizationHeader.StartsWith(AuthSchemeDstsSamlBearer, StringComparison.OrdinalIgnoreCase))
{
// TryAddWithoutValidation method bypasses strict validation, allowing non-standard headers to be added for custom Header schemes that cannot be parsed.
httpRequestMessage.Headers.TryAddWithoutValidation(Authorization, authorizationHeader);
}
else
{
httpRequestMessage.Headers.Add(Authorization, authorizationHeader);
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ namespace Microsoft.Identity.Web.Tests
public class DownstreamApiTests
{
private readonly IAuthorizationHeaderProvider _authorizationHeaderProvider;
private readonly IAuthorizationHeaderProvider _authorizationHeaderProviderSaml;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IOptionsMonitor<DownstreamApiOptions> _namedDownstreamApiOptions;
private readonly ILogger<DownstreamApi> _logger;
private readonly DownstreamApi _input;
private readonly DownstreamApi _inputSaml;

public DownstreamApiTests()
{
_authorizationHeaderProvider = new MyAuthorizationHeaderProvider();
_authorizationHeaderProviderSaml = new MySamlAuthorizationHeaderProvider();
_httpClientFactory = new HttpClientFactoryTest();
_namedDownstreamApiOptions = new MyMonitor();
_logger = new LoggerFactory().CreateLogger<DownstreamApi>();
Expand All @@ -41,6 +44,12 @@ public DownstreamApiTests()
_namedDownstreamApiOptions,
_httpClientFactory,
_logger);

_inputSaml = new DownstreamApi(
_authorizationHeaderProviderSaml,
_namedDownstreamApiOptions,
_httpClientFactory,
_logger);
}

[Fact]
Expand Down Expand Up @@ -123,6 +132,32 @@ public async Task UpdateRequestAsync_WithScopes_AddsAuthorizationHeaderToRequest
Assert.Equal(options.AcquireTokenOptions.ExtraQueryParameters, DownstreamApi.CallerSDKDetails);
}

[Theory]
[InlineData(true)]
[InlineData(false)]

public async Task UpdateRequestAsync_WithScopes_AddsSamlAuthorizationHeaderToRequestAsync(bool appToken)
{
// Arrange
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://example.com");
var content = new StringContent("test content");
var options = new DownstreamApiOptions
{
Scopes = ["scope1", "scope2"],
BaseUrl = "https://localhost:44321/WeatherForecast"
};
var user = new ClaimsPrincipal();

// Act
await _inputSaml.UpdateRequestAsync(httpRequestMessage, content, options, appToken, user, CancellationToken.None);

// Assert
Assert.True(httpRequestMessage.Headers.Contains("Authorization"));
Assert.Equal("http://schemas.microsoft.com/dsts/saml2-bearer ey", httpRequestMessage.Headers.GetValues("Authorization").FirstOrDefault());
Assert.Equal("application/json", httpRequestMessage.Headers.Accept.Single().MediaType);
Assert.Equal(options.AcquireTokenOptions.ExtraQueryParameters, DownstreamApi.CallerSDKDetails);
}

[Fact]
public void SerializeInput_ReturnsCorrectHttpContent()
{
Expand Down Expand Up @@ -420,5 +455,23 @@ public Task<string> CreateAuthorizationHeaderAsync(IEnumerable<string> scopes, A
return Task.FromResult("Bearer ey");
}
}

public class MySamlAuthorizationHeaderProvider : IAuthorizationHeaderProvider
{
public Task<string> CreateAuthorizationHeaderForAppAsync(string scopes, AuthorizationHeaderProviderOptions? downstreamApiOptions = null, CancellationToken cancellationToken = default)
{
return Task.FromResult("http://schemas.microsoft.com/dsts/saml2-bearer ey");
}

public Task<string> CreateAuthorizationHeaderForUserAsync(IEnumerable<string> scopes, AuthorizationHeaderProviderOptions? authorizationHeaderProviderOptions = null, ClaimsPrincipal? claimsPrincipal = null, CancellationToken cancellationToken = default)
{
return Task.FromResult("http://schemas.microsoft.com/dsts/saml2-bearer ey");
}

public Task<string> CreateAuthorizationHeaderAsync(IEnumerable<string> scopes, AuthorizationHeaderProviderOptions? authorizationHeaderProviderOptions = null, ClaimsPrincipal? claimsPrincipal = null, CancellationToken cancellationToken = default)
{
return Task.FromResult("http://schemas.microsoft.com/dsts/saml2-bearer ey");
}
}
}

Loading