Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -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 Down Expand Up @@ -123,6 +148,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 +179,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 +206,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 +223,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 @@ -100,7 +100,8 @@ async Task<Tuple<MsalAccessTokenCacheItem, MsalIdTokenCacheItem, Account>> IToke
instanceDiscoveryMetadata.PreferredCache,
requestParams.AppConfig.ClientId,
response,
homeAccountId)
homeAccountId,
requestParams.CacheKeyComponents)
Comment thread
bgavrilMS marked this conversation as resolved.
Outdated
{
OboCacheKey = CacheKeyFactory.GetOboKey(requestParams.LongRunningOboCacheKey, requestParams.UserAssertion),
};
Expand Down Expand Up @@ -551,6 +552,40 @@ private void FilterTokensByAdditionalKeyComponents(List<MsalAccessTokenCacheItem
}
}

// Symmetric filter for AdditionalCacheKeyComponents on refresh tokens (mirrors AT filter):
// request WITH components -> keep ONLY items with matching components
// request WITHOUT components -> keep ONLY items without components
private void FilterRefreshTokensByAdditionalKeyComponents(List<MsalRefreshTokenCacheItem> refreshTokens, AuthenticationRequestParameters requestParams)
{
bool requestHasComponents =
requestParams.CacheKeyComponents != null &&
requestParams.CacheKeyComponents.Count > 0;

int countBeforeFilter = refreshTokens.Count;

if (requestHasComponents)
{
refreshTokens.FilterWithLogging(item =>
item.AdditionalCacheKeyComponents != null &&
CollectionHelpers.AreDictionariesEqual(item.AdditionalCacheKeyComponents, requestParams.CacheKeyComponents),
requestParams.RequestContext.Logger,
"Filtering RTs by additional key components");
}
else
{
refreshTokens.FilterWithLogging(item =>
item.AdditionalCacheKeyComponents == null ||
item.AdditionalCacheKeyComponents.Count == 0,
requestParams.RequestContext.Logger,
"Filtering out RTs that have additional key components");
}

if (countBeforeFilter > 0 && refreshTokens.Count == 0)
{
requestParams.RequestContext.Logger.Verbose(() => "No RTs found that match the additional key components filter. ");
}
}
Comment thread
iNinja marked this conversation as resolved.

private static void FilterTokensByScopes(
List<MsalAccessTokenCacheItem> tokenCacheItems,
AuthenticationRequestParameters requestParams)
Expand Down Expand Up @@ -842,6 +877,13 @@ async Task<MsalRefreshTokenCacheItem> ITokenCacheInternal.FindRefreshTokenAsync(
{
FilterRefreshTokensByHomeAccountIdOrAssertion(refreshTokens, requestParams, familyId);

// Skip partition filtering for FRT lookups — FRTs are shared across apps
// and are never partitioned, so the partition filter must not apply.
if (string.IsNullOrEmpty(familyId))
{
FilterRefreshTokensByAdditionalKeyComponents(refreshTokens, requestParams);
}

if (!requestParams.AppConfig.MultiCloudSupportEnabled)
{
var metadata =
Expand Down
Loading
Loading