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
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
<MicrosoftIdentityModelVersion Condition="'$(MicrosoftIdentityModelVersion)' == ''">8.19.1</MicrosoftIdentityModelVersion>
<MicrosoftIdentityClientVersion Condition="'$(MicrosoftIdentityClientVersion)' == ''">4.85.2</MicrosoftIdentityClientVersion>
<MicrosoftIdentityClientKeyAttestationVersion Condition="'$(MicrosoftIdentityClientKeyAttestationVersion)' == ''">4.85.2</MicrosoftIdentityClientKeyAttestationVersion>
<MicrosoftIdentityAbstractionsVersion Condition="'$(MicrosoftIdentityAbstractionsVersion)' == ''">12.2.0</MicrosoftIdentityAbstractionsVersion>
<MicrosoftIdentityAbstractionsVersion Condition="'$(MicrosoftIdentityAbstractionsVersion)' == ''">12.3.0</MicrosoftIdentityAbstractionsVersion>
<FxCopAnalyzersVersion>3.3.0</FxCopAnalyzersVersion>
<SystemTextEncodingsWebVersion>4.7.2</SystemTextEncodingsWebVersion>
<AzureSecurityKeyVaultSecretsVersion>4.6.0</AzureSecurityKeyVaultSecretsVersion>
Expand Down
8 changes: 8 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 4.12.0

### New features
- Implement `IAuthorizationHeaderProvider2` (from `Microsoft.Identity.Abstractions` 12.3.0) on `DefaultAuthorizationHeaderProvider` and the public `BaseAuthorizationHeaderProvider`, exposing the metadata-rich `CreateAuthorizationHeaderInformation*` surface (returning `OperationResult<AuthorizationHeaderInformation, AuthorizationHeaderError>`) with binding-certificate propagation. `DownstreamApi` and `MicrosoftIdentityMessageHandler` now prefer `IAuthorizationHeaderProvider2` for mTLS PoP and soft-deprecate the bound-only `IBoundAuthorizationHeaderProvider` path (kept as a fallback for source/binary compatibility).

### Dependencies updates
- Update `Microsoft.Identity.Abstractions` to 12.3.0.

## 4.11.0

### New features
Expand Down
27 changes: 25 additions & 2 deletions src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -713,8 +713,31 @@ public Task<HttpResponseMessage> CallApiForAppAsync(

// Firstly check if it's token binding scenario so authorization header provider returns
// a binding certificate along with acquired authorization header.
if (_authorizationHeaderProvider is IBoundAuthorizationHeaderProvider boundAuthorizationHeaderBoundProvider
&& string.Equals(effectiveOptions.ProtocolScheme, Constants.TokenBindingProtocolScheme, StringComparison.OrdinalIgnoreCase))
// Prefer the IAuthorizationHeaderProvider2 surface (Abstractions 12.3.0+); fall back to the
// legacy IBoundAuthorizationHeaderProvider for custom providers that haven't been updated yet.
bool isTokenBinding = string.Equals(effectiveOptions.ProtocolScheme, Constants.TokenBindingProtocolScheme, StringComparison.OrdinalIgnoreCase);
if (isTokenBinding
&& _authorizationHeaderProvider is IAuthorizationHeaderProvider2 boundAuthorizationHeaderProviderV2)
{
var authorizationHeaderResult = await boundAuthorizationHeaderProviderV2.CreateAuthorizationHeaderInformationAsync(
effectiveOptions.Scopes,
effectiveOptions,
user,
cancellationToken).ConfigureAwait(false);

if (!authorizationHeaderResult.Succeeded)
{
// in theory it shouldn't happen because in case of error during token acquisition
// there will be thrown corresponding exception, so it's more a safeguard
throw new InvalidOperationException("Cannot acquire bound authorization header.");
}

authorizationHeaderInformation = authorizationHeaderResult.Result;
authorizationHeader = authorizationHeaderInformation?.AuthorizationHeaderValue!;
}
// for backwards compatibility.
else if (isTokenBinding
&& _authorizationHeaderProvider is IBoundAuthorizationHeaderProvider boundAuthorizationHeaderBoundProvider)
{
var authorizationHeaderResult = await boundAuthorizationHeaderBoundProvider.CreateBoundAuthorizationHeaderAsync(
effectiveOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ public static Abstractions.AcquireTokenResult FromMsal(AuthenticationResult resu
Metadata = MapMetadata(result),
};

/// <summary>
/// Maps the MSAL <see cref="AuthenticationResultMetadata"/> on an
/// <see cref="AuthenticationResult"/> to its abstractions counterpart.
/// Returns <see langword="null"/> when no metadata was captured.
/// </summary>
public static Abstractions.TokenAcquisitionMetadata? GetMetadata(AuthenticationResult result) =>
MapMetadata(result);

private static Abstractions.TokenAcquisitionMetadata? MapMetadata(AuthenticationResult result)
{
AuthenticationResultMetadata? source = result.AuthenticationResultMetadata;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ namespace Microsoft.Identity.Web.Extensibility
/// Base class for custom implementations of <see cref="IAuthorizationHeaderProvider"/> that
/// would still want to leverage the default implementation for the bearer and Pop protocols.
/// </summary>
public class BaseAuthorizationHeaderProvider : IAuthorizationHeaderProvider
/// <remarks>
/// Also implements <see cref="IAuthorizationHeaderProvider2"/> (added in
/// Microsoft.Identity.Abstractions 12.3.0) so subclasses automatically expose the
/// metadata-rich header-creation surface without needing to opt in. Override the
/// <c>CreateAuthorizationHeaderInformation*</c> virtuals to customize that path.
/// </remarks>
public class BaseAuthorizationHeaderProvider : IAuthorizationHeaderProvider, IAuthorizationHeaderProvider2
{
/// <summary>
/// Constructor from a service provider
/// </summary>
/// <param name="serviceProvider"></param>
public BaseAuthorizationHeaderProvider(IServiceProvider serviceProvider)
public BaseAuthorizationHeaderProvider(IServiceProvider serviceProvider)
{
// We, intentionally, use a locator pattern here, because we don't want to expose ITokenAcquisition
// in the public API as it's going to be deprecated in future versions of IdWeb. Here this
Expand All @@ -30,7 +36,7 @@ public BaseAuthorizationHeaderProvider(IServiceProvider serviceProvider)
_headerProvider = new DefaultAuthorizationHeaderProvider(_tokenAcquisition);
}

private readonly IAuthorizationHeaderProvider _headerProvider;
private readonly DefaultAuthorizationHeaderProvider _headerProvider;

/// <inheritdoc/>
public virtual Task<string> CreateAuthorizationHeaderForUserAsync(IEnumerable<string> scopes, AuthorizationHeaderProviderOptions? authorizationHeaderProviderOptions = null, ClaimsPrincipal? claimsPrincipal = null, CancellationToken cancellationToken = default)
Expand All @@ -46,15 +52,59 @@ public virtual Task<string> CreateAuthorizationHeaderForAppAsync(string scopes,

/// <inheritdoc/>
public virtual Task<string> CreateAuthorizationHeaderAsync(
IEnumerable<string> scopes,
AuthorizationHeaderProviderOptions? authorizationHeaderProviderOptions = null,
ClaimsPrincipal? claimsPrincipal = null,
IEnumerable<string> scopes,
AuthorizationHeaderProviderOptions? authorizationHeaderProviderOptions = null,
ClaimsPrincipal? claimsPrincipal = null,
CancellationToken cancellationToken = default)
{
return _headerProvider.CreateAuthorizationHeaderAsync(
scopes,
authorizationHeaderProviderOptions,
claimsPrincipal,
scopes,
authorizationHeaderProviderOptions,
claimsPrincipal,
cancellationToken);
}

/// <inheritdoc/>
/// <remarks>
/// Default implementation delegates to <see cref="DefaultAuthorizationHeaderProvider"/>; override to inject
/// custom logic while still receiving the binding certificate and token-acquisition metadata for free.
/// </remarks>
public virtual Task<OperationResult<AuthorizationHeaderInformation, AuthorizationHeaderError>> CreateAuthorizationHeaderInformationForUserAsync(
IEnumerable<string> scopes,
AuthorizationHeaderProviderOptions? authorizationHeaderProviderOptions = null,
ClaimsPrincipal? claimsPrincipal = default,
CancellationToken cancellationToken = default)
{
return _headerProvider.CreateAuthorizationHeaderInformationForUserAsync(
scopes,
authorizationHeaderProviderOptions,
claimsPrincipal,
cancellationToken);
}

/// <inheritdoc/>
public virtual Task<OperationResult<AuthorizationHeaderInformation, AuthorizationHeaderError>> CreateAuthorizationHeaderInformationForAppAsync(
string scopes,
AuthorizationHeaderProviderOptions? downstreamApiOptions = null,
CancellationToken cancellationToken = default)
{
return _headerProvider.CreateAuthorizationHeaderInformationForAppAsync(
scopes,
downstreamApiOptions,
cancellationToken);
}

/// <inheritdoc/>
public virtual Task<OperationResult<AuthorizationHeaderInformation, AuthorizationHeaderError>> CreateAuthorizationHeaderInformationAsync(
IEnumerable<string> scopes,
AuthorizationHeaderProviderOptions? options = null,
ClaimsPrincipal? claimsPrincipal = null,
CancellationToken cancellationToken = default)
{
return _headerProvider.CreateAuthorizationHeaderInformationAsync(
scopes,
options,
claimsPrincipal,
cancellationToken);
}
}
Expand Down
Loading
Loading