Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ public static ConfidentialClientApplicationBuilder WithClientCredentials(
credentialSourceLoaderParameters).GetAwaiter().GetResult();
}

#pragma warning disable RS0051
public static async Task<ConfidentialClientApplicationBuilder> WithClientCredentialsAsync(
this ConfidentialClientApplicationBuilder builder,
IEnumerable<CredentialDescription> clientCredentials,
ILogger logger,
ICredentialsLoader credentialsLoader,
CredentialSourceLoaderParameters? credentialSourceLoaderParameters)
CredentialSourceLoaderParameters? credentialSourceLoaderParameters,
IDictionary<string, string>? clientClaims = null)
#pragma warning restore RS0051
{
var credential = await LoadCredentialForMsalOrFailAsync(
clientCredentials,
Expand All @@ -49,7 +52,7 @@ public static async Task<ConfidentialClientApplicationBuilder> WithClientCredent
case CredentialType.SignedAssertion:
return builder.WithClientAssertion((credential.CachedValue as ClientAssertionProviderBase)!.GetSignedAssertionAsync);
case CredentialType.Certificate:
return builder.WithCertificate(credential.Certificate);
return builder.WithCertificateInternal(credential, clientClaims);
case CredentialType.Secret:
return builder.WithClientSecret(credential.ClientSecret);
default:
Expand All @@ -58,6 +61,18 @@ public static async Task<ConfidentialClientApplicationBuilder> WithClientCredent
}
}

private static ConfidentialClientApplicationBuilder WithCertificateInternal(
this ConfidentialClientApplicationBuilder builder,
CredentialDescription credentialDescription,
IDictionary<string, string>? clientClaims = null)
{
if (clientClaims != null && clientClaims.Count > 0)
{
return builder.WithClientClaims(credentialDescription.Certificate, clientClaims);
}
return builder.WithCertificate(credentialDescription.Certificate);
}

internal /* for test */ async static Task<CredentialDescription?> LoadCredentialForMsalOrFailAsync(
IEnumerable<CredentialDescription> clientCredentials,
ILogger logger,
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
#nullable enable
Microsoft.Identity.Web.TokenAcquisition.GetOrBuildConfidentialClientApplicationAsync(Microsoft.Identity.Web.MergedOptions! mergedOptions, Microsoft.Identity.Web.TokenAcquisitionOptions! tokenAcquisitionOptions) -> System.Threading.Tasks.Task<Microsoft.Identity.Client.IConfidentialClientApplication!>!
static Microsoft.Identity.Web.ConfidentialClientApplicationBuilderExtension.WithClientCredentialsAsync(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder! builder, System.Collections.Generic.IEnumerable<Microsoft.Identity.Abstractions.CredentialDescription!>! clientCredentials, Microsoft.Extensions.Logging.ILogger! logger, Microsoft.Identity.Abstractions.ICredentialsLoader! credentialsLoader, Microsoft.Identity.Abstractions.CredentialSourceLoaderParameters? credentialSourceLoaderParameters, System.Collections.Generic.IDictionary<string!, string!>? clientClaims = null) -> System.Threading.Tasks.Task<Microsoft.Identity.Client.ConfidentialClientApplicationBuilder!>!
38 changes: 29 additions & 9 deletions src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,16 +210,22 @@ public async Task<AcquireTokenResult> AddAccountToCacheFromAuthorizationCodeAsyn
}
}


/// <summary>
/// Allows creation of confidential client applications targeting regional and global authorities
/// when supporting managed identities.
/// </summary>
/// <param name="mergedOptions">Merged configuration options</param>
/// <param name="mergedOptions">Merged configuration options.</param>
/// <param name="clientClaims">Optional client claims.</param>
/// <returns>Concatenated string of authority, cliend id and azure region</returns>
private static string GetApplicationKey(MergedOptions mergedOptions)
private static string GetApplicationKey(MergedOptions mergedOptions, IDictionary<string, string>? clientClaims = null)
{
string credentialId = string.Join("-", mergedOptions.ClientCredentials?.Select(c => c.Id) ?? Enumerable.Empty<string>());

if (clientClaims != null)
{
credentialId += "-" + string.Join("-", clientClaims.Select(kvp => $"{kvp.Key}:{kvp.Value}"));
}

return DefaultTokenAcquirerFactoryImplementation.GetKey(mergedOptions.Authority, mergedOptions.ClientId, mergedOptions.AzureRegion) + credentialId;
}

Expand Down Expand Up @@ -258,7 +264,6 @@ public async Task<AuthenticationResult> GetAuthenticationResultForUserAsync(
_ = Throws.IfNull(scopes);

MergedOptions mergedOptions = GetMergedOptions(authenticationScheme, tokenAcquisitionOptions);

user ??= await _tokenAcquisitionHost.GetAuthenticatedUserAsync(user).ConfigureAwait(false);

var application = await GetOrBuildConfidentialClientApplicationAsync(mergedOptions);
Expand Down Expand Up @@ -896,11 +901,25 @@ private bool IsInvalidClientCertificateOrSignedAssertionError(MsalServiceExcepti
);
}

private static IDictionary<string, string>? GetClientClaimsIfExist(TokenAcquisitionOptions? tokenAcquisitionOptions)
{
IDictionary<string, string>? clientClaims = null;
if (tokenAcquisitionOptions is not null && tokenAcquisitionOptions.ExtraParameters is not null &&
tokenAcquisitionOptions.ExtraParameters["IDWEB_CLIENT_CLAIMS"] is not null)
{
clientClaims = tokenAcquisitionOptions.ExtraParameters["IDWEB_CLIENT_CLAIMS"] as IDictionary<string, string>;
}
return clientClaims;
}

#pragma warning disable RS0051 // Add internal types and members to the declared API
internal /* for testing */ async Task<IConfidentialClientApplication> GetOrBuildConfidentialClientApplicationAsync(
MergedOptions mergedOptions)
#pragma warning restore RS0051 // Add internal types and members to the declared API
MergedOptions mergedOptions,
TokenAcquisitionOptions? tokenAcquisitionOptions = null) // just for PoC will drive this through MergedOptions later
{
string key = GetApplicationKey(mergedOptions);
var clientClaims = GetClientClaimsIfExist(tokenAcquisitionOptions);
string key = GetApplicationKey(mergedOptions, clientClaims);

// GetOrAddAsync based on https://github.com/dotnet/runtime/issues/83636#issuecomment-1474998680
// Fast path: check if already created
Expand All @@ -918,7 +937,7 @@ private bool IsInvalidClientCertificateOrSignedAssertionError(MsalServiceExcepti
return app;

// Build and store the application
var newApp = await BuildConfidentialClientApplicationAsync(mergedOptions);
var newApp = await BuildConfidentialClientApplicationAsync(mergedOptions, clientClaims);

// Recompute the key as BuildConfidentialClientApplicationAsync can cause it to change.
key = GetApplicationKey(mergedOptions);
Expand All @@ -934,7 +953,7 @@ private bool IsInvalidClientCertificateOrSignedAssertionError(MsalServiceExcepti
/// <summary>
/// Creates an MSAL confidential client application.
/// </summary>
private async Task<IConfidentialClientApplication> BuildConfidentialClientApplicationAsync(MergedOptions mergedOptions)
private async Task<IConfidentialClientApplication> BuildConfidentialClientApplicationAsync(MergedOptions mergedOptions, IDictionary<string, string>? clientClaims)
{
mergedOptions.PrepareAuthorityInstanceForMsal();

Expand Down Expand Up @@ -991,7 +1010,8 @@ await builder.WithClientCredentialsAsync(
mergedOptions.ClientCredentials!,
_logger,
_credentialsLoader,
new CredentialSourceLoaderParameters(mergedOptions.ClientId!, authority));
new CredentialSourceLoaderParameters(mergedOptions.ClientId!, authority),
clientClaims);
}
catch (ArgumentException ex) when (ex.Message == IDWebErrorMessage.ClientCertificatesHaveExpiredOrCannotBeLoaded)
{
Expand Down
Loading