diff --git a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs
index c8f80eee8..31c02af2d 100644
--- a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs
+++ b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs
@@ -1,176 +1,176 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net.Http;
-using System.Runtime.CompilerServices;
-using System.Security.Claims;
-using System.Text;
-using System.Text.Json;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Runtime.CompilerServices;
+using System.Security.Claims;
+using System.Text;
+using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using Microsoft.Identity.Abstractions;
-using Microsoft.Identity.Client;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Microsoft.Identity.Abstractions;
+using Microsoft.Identity.Client;
using Microsoft.Identity.Web.Diagnostics;
-
-namespace Microsoft.Identity.Web
-{
- ///
- internal partial class DownstreamApi : IDownstreamApi
- {
- private readonly IAuthorizationHeaderProvider _authorizationHeaderProvider;
- private readonly IHttpClientFactory _httpClientFactory;
- private readonly IOptionsMonitor _namedDownstreamApiOptions;
- private const string Authorization = "Authorization";
- protected readonly ILogger _logger;
-
- ///
- /// Constructor.
- ///
- /// Authorization header provider.
- /// Named options provider.
- /// HTTP client factory.
- /// Logger.
- public DownstreamApi(
- IAuthorizationHeaderProvider authorizationHeaderProvider,
- IOptionsMonitor namedDownstreamApiOptions,
- IHttpClientFactory httpClientFactory,
- ILogger logger)
- {
- _authorizationHeaderProvider = authorizationHeaderProvider;
- _namedDownstreamApiOptions = namedDownstreamApiOptions;
- _httpClientFactory = httpClientFactory;
- _logger = logger;
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Task CallApiAsync(
- string? serviceName,
- Action? downstreamApiOptionsOverride = null,
- ClaimsPrincipal? user = null,
- HttpContent? content = null,
- CancellationToken cancellationToken = default)
- {
- DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
- return CallApiInternalAsync(serviceName, effectiveOptions, effectiveOptions.RequestAppToken, content,
- user, cancellationToken);
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Task CallApiAsync(
- DownstreamApiOptions downstreamApiOptions,
- ClaimsPrincipal? user = null,
- HttpContent? content = null,
- CancellationToken cancellationToken = default)
- {
- return CallApiInternalAsync(null, downstreamApiOptions, downstreamApiOptions.RequestAppToken, content,
- user, cancellationToken);
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Task CallApiForUserAsync(
- string? serviceName,
- Action? downstreamApiOptionsOverride = null,
- ClaimsPrincipal? user = null,
- HttpContent? content = null,
- CancellationToken cancellationToken = default)
- {
- DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
- return CallApiInternalAsync(serviceName, effectiveOptions, false, content, user, cancellationToken);
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Task CallApiForAppAsync(
- string? serviceName,
- Action? downstreamApiOptionsOverride = null,
- HttpContent? content = null,
- CancellationToken cancellationToken = default)
- {
- DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
- return CallApiInternalAsync(serviceName, effectiveOptions, true, content, null, cancellationToken);
- }
-
- ///
- public async Task CallApiForUserAsync(
- string? serviceName,
- TInput input,
- Action? downstreamApiOptionsOverride = null,
- ClaimsPrincipal? user = null,
- CancellationToken cancellationToken = default) where TOutput : class
- {
- DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
- HttpContent? effectiveInput = SerializeInput(input, effectiveOptions);
-
- HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false,
- effectiveInput, user, cancellationToken).ConfigureAwait(false);
-
- // Only dispose the HttpContent if was created here, not provided by the caller.
- if (input is not HttpContent)
- {
- effectiveInput?.Dispose();
- }
-
+
+namespace Microsoft.Identity.Web
+{
+ ///
+ internal partial class DownstreamApi : IDownstreamApi
+ {
+ private readonly IAuthorizationHeaderProvider _authorizationHeaderProvider;
+ private readonly IHttpClientFactory _httpClientFactory;
+ private readonly IOptionsMonitor _namedDownstreamApiOptions;
+ private const string Authorization = "Authorization";
+ protected readonly ILogger _logger;
+
+ ///
+ /// Constructor.
+ ///
+ /// Authorization header provider.
+ /// Named options provider.
+ /// HTTP client factory.
+ /// Logger.
+ public DownstreamApi(
+ IAuthorizationHeaderProvider authorizationHeaderProvider,
+ IOptionsMonitor namedDownstreamApiOptions,
+ IHttpClientFactory httpClientFactory,
+ ILogger logger)
+ {
+ _authorizationHeaderProvider = authorizationHeaderProvider;
+ _namedDownstreamApiOptions = namedDownstreamApiOptions;
+ _httpClientFactory = httpClientFactory;
+ _logger = logger;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task CallApiAsync(
+ string? serviceName,
+ Action? downstreamApiOptionsOverride = null,
+ ClaimsPrincipal? user = null,
+ HttpContent? content = null,
+ CancellationToken cancellationToken = default)
+ {
+ DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
+ return CallApiInternalAsync(serviceName, effectiveOptions, effectiveOptions.RequestAppToken, content,
+ user, cancellationToken);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task CallApiAsync(
+ DownstreamApiOptions downstreamApiOptions,
+ ClaimsPrincipal? user = null,
+ HttpContent? content = null,
+ CancellationToken cancellationToken = default)
+ {
+ return CallApiInternalAsync(null, downstreamApiOptions, downstreamApiOptions.RequestAppToken, content,
+ user, cancellationToken);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task CallApiForUserAsync(
+ string? serviceName,
+ Action? downstreamApiOptionsOverride = null,
+ ClaimsPrincipal? user = null,
+ HttpContent? content = null,
+ CancellationToken cancellationToken = default)
+ {
+ DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
+ return CallApiInternalAsync(serviceName, effectiveOptions, false, content, user, cancellationToken);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Task CallApiForAppAsync(
+ string? serviceName,
+ Action? downstreamApiOptionsOverride = null,
+ HttpContent? content = null,
+ CancellationToken cancellationToken = default)
+ {
+ DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
+ return CallApiInternalAsync(serviceName, effectiveOptions, true, content, null, cancellationToken);
+ }
+
+ ///
+ public async Task CallApiForUserAsync(
+ string? serviceName,
+ TInput input,
+ Action? downstreamApiOptionsOverride = null,
+ ClaimsPrincipal? user = null,
+ CancellationToken cancellationToken = default) where TOutput : class
+ {
+ DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
+ HttpContent? effectiveInput = SerializeInput(input, effectiveOptions);
+
+ HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false,
+ effectiveInput, user, cancellationToken).ConfigureAwait(false);
+
+ // Only dispose the HttpContent if was created here, not provided by the caller.
+ if (input is not HttpContent)
+ {
+ effectiveInput?.Dispose();
+ }
+
return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false);
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public async Task CallApiForAppAsync(
- string? serviceName,
- TInput input,
- Action? downstreamApiOptionsOverride = null,
- CancellationToken cancellationToken = default) where TOutput : class
- {
- DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
- HttpContent? effectiveInput = SerializeInput(input, effectiveOptions);
- HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true,
- effectiveInput, null, cancellationToken).ConfigureAwait(false);
-
- // Only dispose the HttpContent if was created here, not provided by the caller.
- if (input is not HttpContent)
- {
- effectiveInput?.Dispose();
- }
-
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public async Task CallApiForAppAsync(
+ string? serviceName,
+ TInput input,
+ Action? downstreamApiOptionsOverride = null,
+ CancellationToken cancellationToken = default) where TOutput : class
+ {
+ DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
+ HttpContent? effectiveInput = SerializeInput(input, effectiveOptions);
+ HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true,
+ effectiveInput, null, cancellationToken).ConfigureAwait(false);
+
+ // Only dispose the HttpContent if was created here, not provided by the caller.
+ if (input is not HttpContent)
+ {
+ effectiveInput?.Dispose();
+ }
+
return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false);
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public async Task CallApiForAppAsync(string serviceName,
- Action? downstreamApiOptionsOverride = null,
- CancellationToken cancellationToken = default) where TOutput : class
- {
- DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
- HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true,
- null, null, cancellationToken).ConfigureAwait(false);
-
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public async Task CallApiForAppAsync(string serviceName,
+ Action? downstreamApiOptionsOverride = null,
+ CancellationToken cancellationToken = default) where TOutput : class
+ {
+ DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
+ HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true,
+ null, null, cancellationToken).ConfigureAwait(false);
+
return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false);
- }
-
- ///
- public async Task CallApiForUserAsync(
- string? serviceName,
- Action? downstreamApiOptionsOverride = null,
- ClaimsPrincipal? user = null,
- CancellationToken cancellationToken = default) where TOutput : class
- {
- DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
- HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false,
- null, user, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ public async Task CallApiForUserAsync(
+ string? serviceName,
+ Action? downstreamApiOptionsOverride = null,
+ ClaimsPrincipal? user = null,
+ CancellationToken cancellationToken = default) where TOutput : class
+ {
+ DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride);
+ HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false,
+ null, user, cancellationToken).ConfigureAwait(false);
return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false);
- }
-
+ }
+
#if NET8_0_OR_GREATER
///
public async Task CallApiForUserAsync(
@@ -264,59 +264,59 @@ public Task CallApiForAppAsync(
}
#endif
- ///
- /// Merge the options from configuration and override from caller.
- ///
- /// Named configuration.
- /// Delegate to override the configuration.
- private /* for tests */ DownstreamApiOptions MergeOptions(
- string? optionsInstanceName,
- Action? calledApiOptionsOverride)
- {
- // Gets the options from configuration (or default value)
- DownstreamApiOptions options;
- if (optionsInstanceName != null)
- {
- options = _namedDownstreamApiOptions.Get(optionsInstanceName);
- }
- else
- {
- options = _namedDownstreamApiOptions.CurrentValue;
- }
-
- DownstreamApiOptions clonedOptions = new DownstreamApiOptions(options);
- calledApiOptionsOverride?.Invoke(clonedOptions);
- return clonedOptions;
- }
-
- ///
- /// Merge the options from configuration and override from caller.
- ///
- /// Named configuration.
- /// Delegate to override the configuration.
- /// Http method overriding the configuration options.
- private DownstreamApiOptions MergeOptions(
- string? optionsInstanceName,
- Action? calledApiOptionsOverride, HttpMethod httpMethod)
- {
- // Gets the options from configuration (or default value)
- DownstreamApiOptions options;
- if (optionsInstanceName != null)
- {
- options = _namedDownstreamApiOptions.Get(optionsInstanceName);
- }
- else
- {
- options = _namedDownstreamApiOptions.CurrentValue;
- }
-
- DownstreamApiOptionsReadOnlyHttpMethod clonedOptions = new DownstreamApiOptionsReadOnlyHttpMethod(options, httpMethod.ToString());
- calledApiOptionsOverride?.Invoke(clonedOptions);
- return clonedOptions;
- }
-
- internal static HttpContent? SerializeInput(TInput input, DownstreamApiOptions effectiveOptions)
- {
+ ///
+ /// Merge the options from configuration and override from caller.
+ ///
+ /// Named configuration.
+ /// Delegate to override the configuration.
+ internal /* for tests */ DownstreamApiOptions MergeOptions(
+ string? optionsInstanceName,
+ Action? calledApiOptionsOverride)
+ {
+ // Gets the options from configuration (or default value)
+ DownstreamApiOptions options;
+ if (optionsInstanceName != null)
+ {
+ options = _namedDownstreamApiOptions.Get(optionsInstanceName);
+ }
+ else
+ {
+ options = _namedDownstreamApiOptions.CurrentValue;
+ }
+
+ DownstreamApiOptions clonedOptions = new DownstreamApiOptions(options);
+ calledApiOptionsOverride?.Invoke(clonedOptions);
+ return clonedOptions;
+ }
+
+ ///
+ /// Merge the options from configuration and override from caller.
+ ///
+ /// Named configuration.
+ /// Delegate to override the configuration.
+ /// Http method overriding the configuration options.
+ internal /* for tests */ DownstreamApiOptions MergeOptions(
+ string? optionsInstanceName,
+ Action? calledApiOptionsOverride, HttpMethod httpMethod)
+ {
+ // Gets the options from configuration (or default value)
+ DownstreamApiOptions options;
+ if (optionsInstanceName != null)
+ {
+ options = _namedDownstreamApiOptions.Get(optionsInstanceName);
+ }
+ else
+ {
+ options = _namedDownstreamApiOptions.CurrentValue;
+ }
+
+ DownstreamApiOptionsReadOnlyHttpMethod clonedOptions = new DownstreamApiOptionsReadOnlyHttpMethod(options, httpMethod.ToString());
+ calledApiOptionsOverride?.Invoke(clonedOptions);
+ return clonedOptions;
+ }
+
+ internal static HttpContent? SerializeInput(TInput input, DownstreamApiOptions effectiveOptions)
+ {
return SerializeInputImpl(input, effectiveOptions, null);
}
@@ -325,11 +325,11 @@ private DownstreamApiOptions MergeOptions(
HttpContent? httpContent;
if (effectiveOptions.Serializer != null)
- {
+ {
httpContent = effectiveOptions.Serializer(input);
- }
- else
- {
+ }
+ else
+ {
// if the input is already an HttpContent, it's used as is, and should already contain a ContentType.
httpContent = input switch
{
@@ -347,13 +347,13 @@ private DownstreamApiOptions MergeOptions(
Encoding.UTF8,
"application/json"),
};
- }
- return httpContent;
- }
-
+ }
+ return httpContent;
+ }
+
internal static async Task DeserializeOutputAsync(HttpResponseMessage response, DownstreamApiOptions effectiveOptions)
- where TOutput : class
- {
+ where TOutput : class
+ {
try
{
response.EnsureSuccessStatusCode();
@@ -405,75 +405,75 @@ private DownstreamApiOptions MergeOptions(
private static async Task DeserializeOutputImplAsync(HttpResponseMessage response, DownstreamApiOptions effectiveOptions, JsonTypeInfo outputJsonTypeInfo)
where TOutput : class
{
- try
- {
- response.EnsureSuccessStatusCode();
- }
- catch
- {
- string error = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
-
-#if NET5_0_OR_GREATER
- throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}", null, response.StatusCode);
-#else
- throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}");
-#endif
- }
-
- HttpContent content = response.Content;
-
- if (content == null)
- {
- return default;
- }
-
- string? mediaType = content.Headers.ContentType?.MediaType;
-
- if (effectiveOptions.Deserializer != null)
- {
- return effectiveOptions.Deserializer(content) as TOutput;
- }
+ try
+ {
+ response.EnsureSuccessStatusCode();
+ }
+ catch
+ {
+ string error = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+
+#if NET5_0_OR_GREATER
+ throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}", null, response.StatusCode);
+#else
+ throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}");
+#endif
+ }
+
+ HttpContent content = response.Content;
+
+ if (content == null)
+ {
+ return default;
+ }
+
+ string? mediaType = content.Headers.ContentType?.MediaType;
+
+ if (effectiveOptions.Deserializer != null)
+ {
+ return effectiveOptions.Deserializer(content) as TOutput;
+ }
else if (typeof(TOutput).IsAssignableFrom(typeof(HttpContent)))
{
return content as TOutput;
- }
- else
- {
- string stringContent = await content.ReadAsStringAsync();
- if (mediaType == "application/json")
- {
+ }
+ else
+ {
+ string stringContent = await content.ReadAsStringAsync();
+ if (mediaType == "application/json")
+ {
return JsonSerializer.Deserialize(stringContent, outputJsonTypeInfo);
}
- if (mediaType != null && !mediaType.StartsWith("text/", StringComparison.OrdinalIgnoreCase))
- {
- // Handle other content types here
- throw new NotSupportedException("Content type not supported. Provide your own deserializer. ");
- }
- return stringContent as TOutput;
- }
- }
-
- private async Task CallApiInternalAsync(
- string? serviceName,
- DownstreamApiOptions effectiveOptions,
- bool appToken,
- HttpContent? content = null,
- ClaimsPrincipal? user = null,
- CancellationToken cancellationToken = default)
- {
- // Downstream API URI
- string apiUrl = effectiveOptions.GetApiUrl();
-
+ if (mediaType != null && !mediaType.StartsWith("text/", StringComparison.OrdinalIgnoreCase))
+ {
+ // Handle other content types here
+ throw new NotSupportedException("Content type not supported. Provide your own deserializer. ");
+ }
+ return stringContent as TOutput;
+ }
+ }
+
+ internal /* for tests */ async Task CallApiInternalAsync(
+ string? serviceName,
+ DownstreamApiOptions effectiveOptions,
+ bool appToken,
+ HttpContent? content = null,
+ ClaimsPrincipal? user = null,
+ CancellationToken cancellationToken = default)
+ {
+ // Downstream API URI
+ string apiUrl = effectiveOptions.GetApiUrl();
+
// Create an HTTP request message
using HttpRequestMessage httpRequestMessage = new(
- new HttpMethod(effectiveOptions.HttpMethod),
- apiUrl);
-
- await UpdateRequestAsync(httpRequestMessage, content, effectiveOptions, appToken, user, cancellationToken);
-
+ new HttpMethod(effectiveOptions.HttpMethod),
+ apiUrl);
+
+ await UpdateRequestAsync(httpRequestMessage, content, effectiveOptions, appToken, user, cancellationToken);
+
using HttpClient client = string.IsNullOrEmpty(serviceName) ? _httpClientFactory.CreateClient() : _httpClientFactory.CreateClient(serviceName);
-
- // Send the HTTP message
+
+ // Send the HTTP message
var downstreamApiResult = await client.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false);
// Retry only if the resource sent 401 Unauthorized with WWW-Authenticate header and claims
@@ -495,7 +495,7 @@ private async Task CallApiInternalAsync(
return downstreamApiResult;
}
-
+
internal /* internal for test */ async Task UpdateRequestAsync(
HttpRequestMessage httpRequestMessage,
HttpContent? content,
@@ -513,29 +513,29 @@ private async Task CallApiInternalAsync(
effectiveOptions.RequestAppToken = appToken;
- // Obtention of the authorization header (except when calling an anonymous endpoint
- // which is done by not specifying any scopes
- if (effectiveOptions.Scopes != null && effectiveOptions.Scopes.Any())
- {
+ // Obtention of the authorization header (except when calling an anonymous endpoint
+ // which is done by not specifying any scopes
+ if (effectiveOptions.Scopes != null && effectiveOptions.Scopes.Any())
+ {
string authorizationHeader = await _authorizationHeaderProvider.CreateAuthorizationHeaderAsync(
effectiveOptions.Scopes,
effectiveOptions,
user,
- cancellationToken).ConfigureAwait(false);
+ cancellationToken).ConfigureAwait(false);
httpRequestMessage.Headers.Add(Authorization, authorizationHeader);
- }
- else
- {
- Logger.UnauthenticatedApiCall(_logger, null);
- }
- if (!string.IsNullOrEmpty(effectiveOptions.AcceptHeader))
+ }
+ else
+ {
+ Logger.UnauthenticatedApiCall(_logger, null);
+ }
+ if (!string.IsNullOrEmpty(effectiveOptions.AcceptHeader))
{
httpRequestMessage.Headers.Accept.ParseAdd(effectiveOptions.AcceptHeader);
- }
- // Opportunity to change the request message
+ }
+ // Opportunity to change the request message
effectiveOptions.CustomizeHttpRequestMessage?.Invoke(httpRequestMessage);
- }
+ }
internal /* for test */ static Dictionary CallerSDKDetails { get; } = new()
{
@@ -557,5 +557,5 @@ private static void AddCallerSDKTelemetry(DownstreamApiOptions effectiveOptions)
CallerSDKDetails["caller-sdk-ver"];
}
}
- }
-}
+ }
+}