Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,10 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net10.0'))">
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>

<!-- Reactivate when https://github.com/AzureAD/microsoft-identity-abstractions-for-dotnet/pull/178 is fixed-->
<!--<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>-->

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(MicrosoftAspNetCoreAuthenticationJwtBearerVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="$(MicrosoftAspNetCoreAuthenticationOpenIdConnectVersion)" />
Expand All @@ -39,7 +34,7 @@
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="$(MicrosoftExtensionsDependencyInjectionVersion)" />
<PackageReference Include="Microsoft.IdentityModel.Logging" Version="$(MicrosoftIdentityModelVersion)" />
Expand All @@ -66,5 +61,5 @@
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/InternalAPI.Shipped.txt" />
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/InternalAPI.Unshipped.txt" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ const Microsoft.Identity.Web.Constants.InvalidClientSecret = "AADSTS7000215" ->
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOf(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, Microsoft.Identity.Web.OnBehalfOfEventArgs! eventArgs) -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOfAsync(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, Microsoft.Identity.Web.OnBehalfOfEventArgs! eventArgs) -> System.Threading.Tasks.Task!
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeOnBehalfOfInitializedAsync(Microsoft.Identity.Web.OnBehalfOfEventArgs! eventArgs) -> System.Threading.Tasks.Task!
Microsoft.Identity.Web.MicrosoftIdentityApplicationOptionsToMergedOptionsMerger
Microsoft.Identity.Web.MicrosoftIdentityApplicationOptionsToMergedOptionsMerger.MicrosoftIdentityApplicationOptionsToMergedOptionsMerger(Microsoft.Identity.Web.IMergedOptionsStore! mergedOptionsStore) -> void
Microsoft.Identity.Web.MicrosoftIdentityApplicationOptionsToMergedOptionsMerger.PostConfigure(string? name, Microsoft.Identity.Abstractions.MicrosoftIdentityApplicationOptions! options) -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOf(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOfAsync(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
static Microsoft.Identity.Web.ConfidentialClientApplicationBuilderExtension.Logger.UsingCertThumbprint(Microsoft.Extensions.Logging.ILogger! logger, string? certThumbprint) -> void
static readonly Microsoft.Identity.Web.Constants.s_certificateRelatedErrorCodes -> System.Collections.Generic.HashSet<string!>!
static readonly Microsoft.Identity.Web.Constants.s_nonRetryableConfigErrorCodes -> System.Collections.Generic.HashSet<string!>!
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ const Microsoft.Identity.Web.Constants.InvalidClientSecret = "AADSTS7000215" ->
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOf(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, Microsoft.Identity.Web.OnBehalfOfEventArgs! eventArgs) -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOfAsync(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, Microsoft.Identity.Web.OnBehalfOfEventArgs! eventArgs) -> System.Threading.Tasks.Task!
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeOnBehalfOfInitializedAsync(Microsoft.Identity.Web.OnBehalfOfEventArgs! eventArgs) -> System.Threading.Tasks.Task!
Microsoft.Identity.Web.Internal.MicrosoftIdentityOptionsBinder
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOf(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOfAsync(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
static Microsoft.Identity.Web.ConfidentialClientApplicationBuilderExtension.Logger.UsingCertThumbprint(Microsoft.Extensions.Logging.ILogger! logger, string? certThumbprint) -> void
static readonly Microsoft.Identity.Web.Constants.s_certificateRelatedErrorCodes -> System.Collections.Generic.HashSet<string!>!
static readonly Microsoft.Identity.Web.Constants.s_nonRetryableConfigErrorCodes -> System.Collections.Generic.HashSet<string!>!
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ public static MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAc
{
if (configuration != null)
{
// TODO: This never was right. And the configureConfidentialClientApplicationOptions delegate is not used
// services.Configure<ConfidentialClientApplicationOptions>(authenticationScheme, configuration);
services.Configure<MicrosoftIdentityApplicationOptions>(authenticationScheme, options
=>
{ configuration.Bind(options); });
Expand Down
193 changes: 193 additions & 0 deletions src/Microsoft.Identity.Web/Internal/IdentityOptionsHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Identity.Abstractions;
using Microsoft.IdentityModel.Tokens;

#if !NETSTANDARD2_0 && !NET462 && !NET472
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Web.Resource;
#endif

namespace Microsoft.Identity.Web.Internal
{
/// <summary>
/// Shared helper methods for identity options configuration and validation.
/// Used by both traditional (MergedOptions) and AOT-compatible paths.
/// </summary>
internal static class IdentityOptionsHelpers
{
/// <summary>
/// Builds the authority URL from the given application options.
/// Handles AAD, B2C, and CIAM scenarios.
/// </summary>
/// <param name="options">The application options containing instance, tenant, and domain information.</param>
/// <returns>The constructed authority URL.</returns>
internal static string BuildAuthority(MicrosoftIdentityApplicationOptions options)
{
if (string.IsNullOrEmpty(options.Instance))
{
throw new ArgumentNullException(nameof(options.Instance));
}

#if !NETSTANDARD2_0 && !NET462 && !NET472
Uri baseUri = new Uri(options.Instance);
var domain = options.Domain;
var tenantId = options.TenantId;

// B2C is detected by presence of SignUpSignInPolicyId
bool isB2C = !string.IsNullOrWhiteSpace(options.SignUpSignInPolicyId);

if (isB2C)
{
var userFlow = options.SignUpSignInPolicyId;
return new Uri(baseUri, new PathString($"{baseUri.PathAndQuery}{domain}/{userFlow}/v2.0")).ToString();
}

return new Uri(baseUri, new PathString($"{baseUri.PathAndQuery}{tenantId}/v2.0")).ToString();
#else
// For non-ASP.NET Core, use simple string concatenation
// options.Instance is guaranteed to be non-null because we check it at the start of the method
var instance = options.Instance!.TrimEnd('/');
bool isB2C = !string.IsNullOrWhiteSpace(options.SignUpSignInPolicyId);

if (isB2C)
{
return $"{instance}/{options.Domain}/{options.SignUpSignInPolicyId}/v2.0";
}

return $"{instance}/{options.TenantId}/v2.0";
#endif
}


#if !NETSTANDARD2_0 && !NET462 && !NET472
/// <summary>
/// Configures issuer validation on the JWT bearer options.
/// Sets up multi-tenant issuer validation logic that accepts both v1.0 and v2.0 tokens.
/// If the developer has already registered an IssuerValidator, it will not be overwritten.
/// </summary>
/// <param name="options">The JWT bearer options containing token validation parameters and authority.</param>
/// <param name="serviceProvider">The service provider to resolve the issuer validator factory.</param>
internal static void ConfigureIssuerValidation(
JwtBearerOptions options,
IServiceProvider serviceProvider)
{
if (options.TokenValidationParameters.ValidateIssuer &&
options.TokenValidationParameters.IssuerValidator == null)
{
var microsoftIdentityIssuerValidatorFactory =
serviceProvider.GetRequiredService<MicrosoftIdentityIssuerValidatorFactory>();

options.TokenValidationParameters.IssuerValidator =
microsoftIdentityIssuerValidatorFactory.GetAadIssuerValidator(options.Authority!).Validate;
}
}

/// <summary>
/// Ensures the JwtBearerOptions.Events object exists and wires up the
/// ConfigurationManager on the OnMessageReceived event.
/// </summary>
/// <param name="options">The JWT bearer options to configure.</param>
internal static void InitializeJwtBearerEvents(JwtBearerOptions options)
{
if (options.Events == null)
{
options.Events = new JwtBearerEvents();
}

var existingOnMessageReceived = options.Events.OnMessageReceived;
options.Events.OnMessageReceived = async context =>
{
context.Options.TokenValidationParameters.ConfigurationManager ??=
options.ConfigurationManager as BaseConfigurationManager;

if (existingOnMessageReceived != null)
{
await existingOnMessageReceived(context).ConfigureAwait(false);
}
};
}

/// <summary>
/// Configures audience validation on the token validation parameters if not already configured.
/// Sets up custom validator for handling v1.0/v2.0 and B2C tokens correctly.
/// This is AOT-compatible as it directly sets up the validator without using reflection or MicrosoftIdentityOptions.
/// </summary>
/// <param name="validationParameters">The token validation parameters to configure.</param>
/// <param name="clientId">The application (client) ID.</param>
/// <param name="isB2C">Whether the application targets Azure AD B2C.</param>
internal static void ConfigureAudienceValidation(
TokenValidationParameters validationParameters,
string? clientId,
bool isB2C)
{
// Skip if audience validation is already configured by the caller
if (validationParameters.AudienceValidator != null ||
validationParameters.ValidAudience != null ||
validationParameters.ValidAudiences != null)
{
return;
}

// Set up the audience validator directly without converting to MicrosoftIdentityOptions
validationParameters.AudienceValidator = (audiences, securityToken, validationParams) =>
{
var claims = securityToken switch
{
System.IdentityModel.Tokens.Jwt.JwtSecurityToken jwtSecurityToken => jwtSecurityToken.Claims,
Microsoft.IdentityModel.JsonWebTokens.JsonWebToken jwtWebToken => jwtWebToken.Claims,
_ => throw new SecurityTokenValidationException(IDWebErrorMessage.TokenIsNotJwtToken),
};

validationParams.AudienceValidator = null;

// Case of a default App ID URI (the developer did not provide explicit valid audience(s))
if (string.IsNullOrEmpty(validationParams.ValidAudience) &&
validationParams.ValidAudiences == null)
{
// handle v2.0 access token or Azure AD B2C tokens (even if v1.0)
if (isB2C || claims.Any(c => c.Type == Constants.Version && c.Value == Constants.V2))
{
validationParams.ValidAudience = $"{clientId}";
}
// handle v1.0 access token
else if (claims.Any(c => c.Type == Constants.Version && c.Value == Constants.V1))
{
validationParams.ValidAudience = $"api://{clientId}";
}
}

Validators.ValidateAudience(audiences, securityToken, validationParams);
return true;
};
}

/// <summary>
/// Chains a handler onto the OnTokenValidated event to store the token for OBO scenarios.
/// </summary>
/// <param name="existingHandler">The existing OnTokenValidated handler, if any.</param>
/// <returns>A new handler that stores the token and then calls the existing handler.</returns>
internal static Func<TokenValidatedContext, Task> ChainTokenStorageHandler(Func<TokenValidatedContext, Task>? existingHandler)
{
return async context =>
{
// Only pass through a token if it is of an expected type
context.HttpContext.StoreTokenUsedToCallWebAPI(
context.SecurityToken is System.IdentityModel.Tokens.Jwt.JwtSecurityToken or
Microsoft.IdentityModel.JsonWebTokens.JsonWebToken ? context.SecurityToken : null);

if (existingHandler != null)
{
await existingHandler(context).ConfigureAwait(false);
}
};
}
#endif
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -22,8 +21,6 @@ public static class RequiredScopeOrAppPermissionExtensions
/// </summary>
/// <param name="services">The services being configured.</param>
/// <returns>Services.</returns>
[RequiresUnreferencedCode("Calls Microsoft.Extensions.Configuration.ConfigurationBinder.GetValue")]
[RequiresDynamicCode("Calls Microsoft.Extensions.Configuration.ConfigurationBinder.GetValue")]
public static IServiceCollection AddRequiredScopeOrAppPermissionAuthorization(this IServiceCollection services)
{
services.AddAuthorization();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
Expand All @@ -18,8 +17,6 @@ namespace Microsoft.Identity.Web
/// Scope or app permission authorization handler that needs to be called for a specific requirement type.
/// In this case, <see cref="ScopeOrAppPermissionAuthorizationRequirement"/>.
/// </summary>
[RequiresUnreferencedCode("Calls Microsoft.Extensions.Configuration.ConfigurationBinder.GetValue")]
[RequiresDynamicCode("Calls Microsoft.Extensions.Configuration.ConfigurationBinder.GetValue")]
internal class ScopeOrAppPermissionAuthorizationHandler : AuthorizationHandler<ScopeOrAppPermissionAuthorizationRequirement>
{
private readonly IConfiguration _configuration;
Expand Down Expand Up @@ -77,7 +74,7 @@ protected override Task HandleRequirementAsync(

if (appPermissionConfigurationKey != null)
{
appPermissions = _configuration.GetValue<string>(appPermissionConfigurationKey)?.Split(' ');
appPermissions = _configuration[appPermissionConfigurationKey]?.Split(' ');
}

if (appPermissions is null)
Expand Down
Loading
Loading