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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithExtraClientAssertionClaims(System.String) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ internal class AcquireTokenCommonParameters
public string FmiPathSuffix { get; internal set; }
public string ClientAssertionFmiPath { get; internal set; }
public bool IsMtlsPopRequested { get; set; }
public string ExtraClientAssertionClaims { get; internal set; }

/// <summary>
/// Optional delegate for obtaining attestation JWT for Credential Guard keys.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ public ConfidentialClientApplicationBuilder WithCertificate(X509Certificate2 cer
/// You should use certificates with a private key size of at least 2048 bytes. Future versions of this library might reject certificates with smaller keys.
/// Does not send the certificate (as x5c parameter) with the request by default.
/// </remarks>
[Obsolete("This method is obsolete. Use the WithExtraClientAssertionClaims method on AcquireTokenForClientParameterBuilder", false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public ConfidentialClientApplicationBuilder WithClientClaims(X509Certificate2 certificate, IDictionary<string, string> claimsToSign, bool mergeWithDefaultClaims)
{
return WithClientClaims(certificate, claimsToSign, mergeWithDefaultClaims, false);
Expand All @@ -157,6 +159,8 @@ public ConfidentialClientApplicationBuilder WithClientClaims(X509Certificate2 ce
/// <param name="mergeWithDefaultClaims">Determines whether or not to merge <paramref name="claimsToSign"/> with the default claims required for authentication.</param>
/// <param name="sendX5C">To send X5C with every request or not.</param>
/// <remarks>You should use certificates with a private key size of at least 2048 bytes. Future versions of this library might reject certificates with smaller keys.</remarks>
[Obsolete("This method is obsolete. Use the WithExtraClientAssertionClaims method on AcquireTokenForClientParameterBuilder", false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public ConfidentialClientApplicationBuilder WithClientClaims(X509Certificate2 certificate, IDictionary<string, string> claimsToSign, bool mergeWithDefaultClaims = true, bool sendX5C = false)
{
if (certificate == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,5 +204,39 @@ public static AbstractAcquireTokenParameterBuilder<T> WithFmiPathForClientAssert

return builder;
}

/// <summary>
/// Specifies extra claims to be included in the client assertion.
/// These claims will be merged with default claims when the client assertion is generated.
/// This lets higher level APIs like Microsoft.Identity.Web provide additional claims for the client assertion.
/// Important: tokens are associated with the extra client assertion claims, which impacts cache lookups.
/// This is an extensibility API and should not be used by applications directly.
/// </summary>
/// <param name="builder">The builder to chain options to</param>
/// <param name="clientAssertionClaims">Additional claims in JSON format to be signed in the client assertion.</param>
/// <returns>The builder to chain the .With methods</returns>
/// <exception cref="ArgumentNullException">Thrown when clientAssertionClaims is null or whitespace.</exception>
public static AbstractAcquireTokenParameterBuilder<T> WithExtraClientAssertionClaims<T>(
this AbstractAcquireTokenParameterBuilder<T> builder,
string clientAssertionClaims)
where T : AbstractAcquireTokenParameterBuilder<T>
{
if (string.IsNullOrWhiteSpace(clientAssertionClaims))
{
throw new ArgumentNullException(nameof(clientAssertionClaims));
}

builder.CommonParameters.ExtraClientAssertionClaims = clientAssertionClaims;

// Add the extra claims to the cache key so different claims result in different cache entries
var cacheKey = new SortedList<string, Func<CancellationToken, Task<string>>>
{
{ "extra_client_assertion_claims", (CancellationToken ct) => Task.FromResult(clientAssertionClaims) }
};

WithAdditionalCacheKeyComponents(builder, cacheKey);

return builder;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Microsoft.Identity.Client.Internal.ClientCredential
internal class CertificateAndClaimsClientCredential : IClientCredential
{
private readonly IDictionary<string, string> _claimsToSign;
private readonly bool _appendDefaultClaims;
private readonly bool _appendDefaultClaims = true;
private readonly Func<AssertionRequestOptions, Task<X509Certificate2>> _certificateProvider;

public AssertionType AssertionType => AssertionType.CertificateWithoutSni;
Expand Down Expand Up @@ -72,12 +72,25 @@ public async Task AddConfidentialClientParametersAsync(

bool useSha2 = requestParameters.AuthorityManager.Authority.AuthorityInfo.IsSha2CredentialSupported;

var jwtToken = new JsonWebToken(
cryptographyManager,
clientId,
tokenEndpoint,
_claimsToSign,
_appendDefaultClaims);
JsonWebToken jwtToken;
if (string.IsNullOrEmpty(requestParameters.ExtraClientAssertionClaims))
{
jwtToken = new JsonWebToken(
cryptographyManager,
clientId,
tokenEndpoint,
_claimsToSign,
_appendDefaultClaims);
}
else
{
jwtToken = new JsonWebToken(
cryptographyManager,
clientId,
tokenEndpoint,
requestParameters.ExtraClientAssertionClaims,
_appendDefaultClaims);
}

string assertion = jwtToken.Sign(certificate, requestParameters.SendX5C, useSha2);

Expand Down
84 changes: 70 additions & 14 deletions src/client/Microsoft.Identity.Client/Internal/JsonWebToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Microsoft.Identity.Client.PlatformsCommon.Interfaces;
using Microsoft.Identity.Client.Utils;
using System.Security.Cryptography;
#if SUPPORTS_SYSTEM_TEXT_JSON
using JObject = System.Text.Json.Nodes.JsonObject;
using System.Text.Json;
#else
using Microsoft.Identity.Json.Linq;
#endif
Expand All @@ -23,6 +24,7 @@ internal class JsonWebToken
public const long JwtToAadLifetimeInSeconds = 60 * 10; // Ten minutes

private readonly IDictionary<string, string> _claimsToSign;
private readonly string _claimsToSignJson;
private readonly ICryptographyManager _cryptographyManager;
private readonly string _clientId;
private readonly string _audience;
Expand All @@ -47,12 +49,27 @@ public JsonWebToken(
_appendDefaultClaims = appendDefaultClaims;
}

public JsonWebToken(
ICryptographyManager cryptographyManager,
string clientId,
string audience,
string claimsToSignJson,
bool appendDefaultClaims = false)
: this(cryptographyManager, clientId, audience)
{
_claimsToSignJson = claimsToSignJson;
_appendDefaultClaims = appendDefaultClaims;
}

private string CreateJsonPayload()
{
long validFrom = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
long validTo = validFrom + JwtToAadLifetimeInSeconds; // 10 min

if (_claimsToSign == null || _claimsToSign.Count == 0)
bool hasClaimsFromDictionary = _claimsToSign != null && _claimsToSign.Count > 0;
bool hasClaimsFromJson = !string.IsNullOrWhiteSpace(_claimsToSignJson);

if (!hasClaimsFromDictionary && !hasClaimsFromJson)
{
return $$"""{"aud":"{{_audience}}","iss":"{{_clientId}}","sub":"{{_clientId}}","nbf":"{{validFrom}}","exp":"{{validTo}}","jti":"{{Guid.NewGuid()}}"}""";
}
Expand All @@ -70,17 +87,56 @@ private string CreateJsonPayload()
payload.Append('{');
}

var json = new JObject();

foreach (var claim in _claimsToSign)
// Handle claims from JSON string
if (hasClaimsFromJson)
{
json[claim.Key] = claim.Value;
// Remove outer braces from JSON string and append
string jsonClaims = _claimsToSignJson.Trim();
if (jsonClaims.StartsWith("{") && jsonClaims.EndsWith("}"))
{
jsonClaims = jsonClaims.Substring(1, jsonClaims.Length - 2);
}

payload.Append(jsonClaims);
}

var jsonClaims = JsonHelper.JsonObjectToString(json);
// Handle claims from dictionary
else if (hasClaimsFromDictionary)
{
#if SUPPORTS_SYSTEM_TEXT_JSON
using (var stream = new MemoryStream())
{
using (var writer = new Utf8JsonWriter(stream))
{
writer.WriteStartObject();

foreach (var claim in _claimsToSign)
{
writer.WriteString(claim.Key, claim.Value);
}

writer.WriteEndObject();
}

var jsonClaims = Encoding.UTF8.GetString(stream.ToArray());

//Remove extra brackets from JSON result
payload.Append(jsonClaims.Substring(1, jsonClaims.Length - 2));
}
#else
var json = new JObject();

foreach (var claim in _claimsToSign)
{
json[claim.Key] = claim.Value;
}

var jsonClaims = JsonHelper.JsonObjectToString(json);

//Remove extra brackets from JSON result
payload.Append(jsonClaims.Substring(1, jsonClaims.Length - 2));
//Remove extra brackets from JSON result
payload.Append(jsonClaims.Substring(1, jsonClaims.Length - 2));
#endif
}

payload.Append('}');

Expand Down Expand Up @@ -146,11 +202,11 @@ private static string ComputeCertThumbprint(X509Certificate2 certificate, bool u

thumbprint = Base64UrlHelpers.Encode(certificate.GetCertHash(HashAlgorithmName.SHA256));
#else
using (var hasher = SHA256.Create())
{
byte[] hash = hasher.ComputeHash(certificate.RawData);
thumbprint = Base64UrlHelpers.Encode(hash);
}
using (var hasher = SHA256.Create())
{
byte[] hash = hasher.ComputeHash(certificate.RawData);
thumbprint = Base64UrlHelpers.Encode(hash);
}
#endif
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ public string LoginHint
public string ClientAssertionFmiPath => _commonParameters.ClientAssertionFmiPath;
#endregion

public string ExtraClientAssertionClaims => _commonParameters.ExtraClientAssertionClaims;

public void LogParameters()
{
var logger = RequestContext.Logger;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
static Microsoft.Identity.Client.Extensibility.AbstractConfidentialClientAcquireTokenParameterBuilderExtension.WithExtraClientAssertionClaims<T>(this Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder<T> builder, string clientAssertionClaims) -> Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder<T>
Microsoft.Identity.Client.ManagedIdentityPopExtensions
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithAttributes(string attributeJson) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
static Microsoft.Identity.Client.Extensibility.AbstractConfidentialClientAcquireTokenParameterBuilderExtension.WithExtraClientAssertionClaims<T>(this Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder<T> builder, string clientAssertionClaims) -> Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder<T>
Microsoft.Identity.Client.ManagedIdentityPopExtensions
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithAttributes(string attributeJson) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
static Microsoft.Identity.Client.Extensibility.AbstractConfidentialClientAcquireTokenParameterBuilderExtension.WithExtraClientAssertionClaims<T>(this Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder<T> builder, string clientAssertionClaims) -> Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder<T>
Microsoft.Identity.Client.ManagedIdentityPopExtensions
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithAttributes(string attributeJson) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
static Microsoft.Identity.Client.Extensibility.AbstractConfidentialClientAcquireTokenParameterBuilderExtension.WithExtraClientAssertionClaims<T>(this Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder<T> builder, string clientAssertionClaims) -> Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder<T>
Microsoft.Identity.Client.ManagedIdentityPopExtensions
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithAttributes(string attributeJson) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
static Microsoft.Identity.Client.Extensibility.AbstractConfidentialClientAcquireTokenParameterBuilderExtension.WithExtraClientAssertionClaims<T>(this Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder<T> builder, string clientAssertionClaims) -> Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder<T>
Microsoft.Identity.Client.ManagedIdentityPopExtensions
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithAttributes(string attributeJson) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
static Microsoft.Identity.Client.Extensibility.AbstractConfidentialClientAcquireTokenParameterBuilderExtension.WithExtraClientAssertionClaims<T>(this Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder<T> builder, string clientAssertionClaims) -> Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder<T>
Microsoft.Identity.Client.ManagedIdentityPopExtensions
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithAttributes(string attributeJson) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -443,13 +443,14 @@ private static IConfidentialClientApplication CreateApp(
aud2,
cert));
break;

#pragma warning disable CS0618 // Type or member is obsolete
case CredentialType.ClientClaims_ExtraClaims:
builder.WithClientClaims(cert, GetClaims(true), mergeWithDefaultClaims: false, sendX5C: sendX5C);
break;
case CredentialType.ClientClaims_MergeClaims:
builder.WithClientClaims(cert, GetClaims(false), mergeWithDefaultClaims: true, sendX5C: sendX5C);
break;
#pragma warning restore CS0618 // Type or member is obsolete
default:
throw new NotImplementedException();
}
Expand Down
Loading