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
Expand Up @@ -40,6 +40,7 @@ internal class AcquireTokenCommonParameters
public X509Certificate2 MtlsCertificate { get; internal set; }
public List<string> AdditionalCacheParameters { get; set; }
public SortedList<string, Func<CancellationToken, Task<string>>> CacheKeyComponents { get; internal set; }
public bool PartitionRefreshToken { get; internal set; }
public bool? SendOfflineAccessScope { get; set; }
public string FmiPathSuffix { get; internal set; }
public string ClientAssertionFmiPath { get; internal set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Identity.Client.Cache.Keys;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.Utils;
Expand All @@ -20,14 +22,16 @@ internal MsalRefreshTokenCacheItem(
string preferredCacheEnv,
string clientId,
MsalTokenResponse response,
string homeAccountId)
string homeAccountId,
SortedList<string, string> cacheKeyComponents = null)
: this(
preferredCacheEnv,
clientId,
response.RefreshToken,
response.ClientInfo,
response.FamilyId,
homeAccountId)
homeAccountId,
cacheKeyComponents)
{
}

Expand All @@ -37,7 +41,8 @@ internal MsalRefreshTokenCacheItem(
string secret,
string rawClientInfo,
string familyId,
string homeAccountId)
string homeAccountId,
SortedList<string, string> cacheKeyComponents = null)
: this()
{
ClientId = clientId;
Expand All @@ -47,6 +52,12 @@ internal MsalRefreshTokenCacheItem(
FamilyId = familyId;
HomeAccountId = homeAccountId;

// Do not partition FRTs — they are shared across apps by design
if (string.IsNullOrWhiteSpace(FamilyId) && cacheKeyComponents != null && cacheKeyComponents.Any())
{
AdditionalCacheKeyComponents = cacheKeyComponents;
}

InitCacheKey();
}

Expand All @@ -61,6 +72,20 @@ internal void InitCacheKey()
key = $"{HomeAccountId}{d}{Environment}{d}{StorageJsonValues.CredentialTypeRefreshToken}{d}{FamilyId}{d}{d}".ToLowerInvariant();

}
else if (AdditionalCacheKeyComponents != null && AdditionalCacheKeyComponents.Count > 0)
{
// Pass the partition hash through additionalKeys so it is included
// in the ToLowerInvariant() call inside GetCredentialKey, keeping
// cache key casing consistent with the AT partition pattern.
key = MsalCacheKeys.GetCredentialKey(
Comment thread
iNinja marked this conversation as resolved.
HomeAccountId,
Environment,
StorageJsonValues.CredentialTypeRefreshToken,
ClientId,
tenantId: null,
scopes: null,
CoreHelpers.ComputeAccessTokenExtCacheKey(AdditionalCacheKeyComponents));
}
else
{
key = MsalCacheKeys.GetCredentialKey(
Expand All @@ -79,13 +104,18 @@ internal void InitCacheKey()

internal string ToLogString(bool piiEnabled = false)
{
string additionalKeys = AdditionalCacheKeyComponents != null && AdditionalCacheKeyComponents.Count > 0
? CoreHelpers.ComputeAccessTokenExtCacheKey(AdditionalCacheKeyComponents)
: null;

return MsalCacheKeys.GetCredentialKey(
piiEnabled ? HomeAccountId : HomeAccountId?.GetHashCode().ToString(),
Environment,
StorageJsonValues.CredentialTypeRefreshToken,
ClientId,
tenantId: null,
scopes: null);
scopes: null,
additionalKeys);
}

#region iOS
Expand Down Expand Up @@ -123,6 +153,16 @@ public string GetiOSService()
return $"{StorageJsonValues.CredentialTypeRefreshToken}{MsalCacheKeys.CacheKeyDelimiter}{FamilyId}{MsalCacheKeys.CacheKeyDelimiter}{MsalCacheKeys.CacheKeyDelimiter}".ToLowerInvariant();
}

if (AdditionalCacheKeyComponents != null && AdditionalCacheKeyComponents.Count > 0)
{
return MsalCacheKeys.GetiOSServiceKey(
StorageJsonValues.CredentialTypeRefreshToken,
ClientId,
tenantId: null,
scopes: null,
CoreHelpers.ComputeAccessTokenExtCacheKey(AdditionalCacheKeyComponents));
}

return MsalCacheKeys.GetiOSServiceKey(StorageJsonValues.CredentialTypeRefreshToken, ClientId, tenantId: null, scopes: null);
}

Expand All @@ -144,6 +184,12 @@ public string GetiOSService()
/// </summary>
public bool IsFRT => !string.IsNullOrEmpty(FamilyId);

/// <summary>
/// Additional key-value components used to partition this RT in the cache.
/// Never set on FRTs (family refresh tokens are shared across apps).
/// </summary>
internal SortedList<string, string> AdditionalCacheKeyComponents { get; private set; }
Comment thread
iNinja marked this conversation as resolved.

public string CacheKey { get; private set; }

private Lazy<IiOSKey> iOSCacheKeyLazy;
Expand All @@ -165,6 +211,12 @@ internal static MsalRefreshTokenCacheItem FromJObject(JObject j)
item.FamilyId = JsonHelper.ExtractExistingOrEmptyString(j, StorageJsonKeys.FamilyId);
item.OboCacheKey = JsonHelper.ExtractExistingOrEmptyString(j, StorageJsonKeys.UserAssertionHash);

var additionalCacheKeyComponents = JsonHelper.ExtractInnerJsonAsDictionary(j, StorageJsonKeys.CacheExtensions);
if (additionalCacheKeyComponents != null && string.IsNullOrWhiteSpace(item.FamilyId))
{
item.AdditionalCacheKeyComponents = new SortedList<string, string>(additionalCacheKeyComponents);
}

item.PopulateFieldsFromJObject(j);
item.InitCacheKey();

Expand All @@ -176,9 +228,28 @@ internal override JObject ToJObject()
var json = base.ToJObject();
SetItemIfValueNotNull(json, StorageJsonKeys.FamilyId, FamilyId);
SetItemIfValueNotNull(json, StorageJsonKeys.UserAssertionHash, OboCacheKey);

if (AdditionalCacheKeyComponents != null && AdditionalCacheKeyComponents.Count > 0)
{
StoreDictionaryInJson(json, StorageJsonKeys.CacheExtensions, AdditionalCacheKeyComponents);
}

return json;
}

private static void StoreDictionaryInJson(JObject json, string key, IDictionary<string, string> values)
{
if (values != null)
{
var innerJson = new JObject();
foreach (var kvp in values)
{
innerJson[kvp.Key] = kvp.Value;
}
json[key] = innerJson;
}
}

internal string ToJsonString()
{
return ToJObject().ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public static T WithExtraHttpHeaders<T>(

/// <summary>
/// Adds a key-value pair to the token cache key without sending it as a query parameter.
/// Use this to partition cached tokens (e.g., isolating short-lived sessions from regular
/// Use this to partition cached access tokens (e.g., isolating short-lived sessions from regular
/// sessions for the same user). Both <c>AcquireTokenByAuthorizationCode</c> and
/// <c>AcquireTokenSilent</c> must use the same partition key to match cached entries.
/// </summary>
Expand All @@ -66,6 +66,31 @@ public static T WithCachePartitionKey<T>(
string key,
string value)
where T : BaseAbstractAcquireTokenParameterBuilder<T>
{
return builder.WithCachePartitionKey(key, value, partitionRefreshToken: false);
}

/// <summary>
/// Adds a key-value pair to the token cache key without sending it as a query parameter.
/// Use this to partition cached tokens (e.g., isolating short-lived sessions from regular
/// sessions for the same user). Both <c>AcquireTokenByAuthorizationCode</c> and
/// <c>AcquireTokenSilent</c> must use the same partition key to match cached entries.
/// </summary>
/// <param name="builder">The builder to chain .With methods.</param>
/// <param name="key">The partition key name.</param>
/// <param name="value">The partition key value.</param>
/// <param name="partitionRefreshToken">
/// When <see langword="true"/>, the refresh token is also stored and looked up using
/// the partition key. When <see langword="false"/>, only the access token is partitioned
/// and the refresh token remains in the shared pool.
/// </param>
/// <returns>The builder to chain .With methods.</returns>
public static T WithCachePartitionKey<T>(
this BaseAbstractAcquireTokenParameterBuilder<T> builder,
string key,
string value,
bool partitionRefreshToken)
where T : BaseAbstractAcquireTokenParameterBuilder<T>
{
if (key is null)
{
Expand All @@ -85,6 +110,7 @@ public static T WithCachePartitionKey<T>(
builder.CommonParameters.CacheKeyComponents ??= new SortedList<string, Func<CancellationToken, Task<string>>>();
string capturedValue = value;
builder.CommonParameters.CacheKeyComponents[key] = (CancellationToken _) => Task.FromResult(capturedValue);
builder.CommonParameters.PartitionRefreshToken |= partitionRefreshToken;
return (T)builder;
Comment thread
Copilot marked this conversation as resolved.
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public AuthenticationRequestParameters(

HomeAccountId = homeAccountId;
CacheKeyComponents = cacheKeyComponents;
PartitionRefreshToken = commonParameters.PartitionRefreshToken;

// Defer JSON merge to first access — cache hits never read ClaimsAndClientCapabilities,
// so we avoid parsing on the hot path.
Expand Down Expand Up @@ -165,6 +166,8 @@ public IAuthenticationOperation AuthenticationScheme

public SortedList<string, string> CacheKeyComponents {get; private set; }

public bool PartitionRefreshToken { get; private set; }

#region TODO REMOVE FROM HERE AND USE FROM SPECIFIC REQUEST PARAMETERS
// TODO: ideally, these can come from the particular request instance and not be in RequestBase since it's not valid for all requests.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Microsoft.Identity.Client.AppConfig.PoPOptions
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
Microsoft.Identity.Client.AppConfig.PoPOptions
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.set -> void
Microsoft.Identity.Client.AppConfig.PoPOptions.PoPOptions() -> void
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey<T>(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder<T> builder, string key, string value, bool partitionRefreshToken) -> T
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder, Microsoft.Identity.Client.AppConfig.PoPOptions options) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Microsoft.Identity.Client.AppConfig.PoPOptions
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
Microsoft.Identity.Client.AppConfig.PoPOptions
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.set -> void
Microsoft.Identity.Client.AppConfig.PoPOptions.PoPOptions() -> void
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey<T>(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder<T> builder, string key, string value, bool partitionRefreshToken) -> T
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder, Microsoft.Identity.Client.AppConfig.PoPOptions options) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Microsoft.Identity.Client.AppConfig.PoPOptions
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
Microsoft.Identity.Client.AppConfig.PoPOptions
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.set -> void
Microsoft.Identity.Client.AppConfig.PoPOptions.PoPOptions() -> void
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey<T>(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder<T> builder, string key, string value, bool partitionRefreshToken) -> T
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder, Microsoft.Identity.Client.AppConfig.PoPOptions options) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Microsoft.Identity.Client.AppConfig.PoPOptions
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
Microsoft.Identity.Client.AppConfig.PoPOptions
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.set -> void
Microsoft.Identity.Client.AppConfig.PoPOptions.PoPOptions() -> void
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey<T>(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder<T> builder, string key, string value, bool partitionRefreshToken) -> T
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder, Microsoft.Identity.Client.AppConfig.PoPOptions options) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Microsoft.Identity.Client.AppConfig.PoPOptions
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
Microsoft.Identity.Client.AppConfig.PoPOptions
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.set -> void
Microsoft.Identity.Client.AppConfig.PoPOptions.PoPOptions() -> void
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey<T>(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder<T> builder, string key, string value, bool partitionRefreshToken) -> T
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder, Microsoft.Identity.Client.AppConfig.PoPOptions options) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Microsoft.Identity.Client.AppConfig.PoPOptions
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
Microsoft.Identity.Client.AppConfig.PoPOptions
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.set -> void
Microsoft.Identity.Client.AppConfig.PoPOptions.PoPOptions() -> void
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey<T>(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder<T> builder, string key, string value, bool partitionRefreshToken) -> T
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder, Microsoft.Identity.Client.AppConfig.PoPOptions options) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Loading
Loading