Add refresh token cache partitioning support#6077
Merged
Merged
Conversation
Extend the existing access token cache partition mechanism to refresh tokens, allowing callers who use WithCachePartitionKey to also partition their RTs. Changes: - MsalRefreshTokenCacheItem: add AdditionalCacheKeyComponents property, SHA256 hash appended to cache key, JSON serialization under 'ext' field - TokenCache: thread CacheKeyComponents to RT constructor, add FilterRefreshTokensByAdditionalKeyComponents filter in FindRefreshTokenAsync - FRT guard: Family Refresh Tokens are never partitioned (shared by design); partition filter is skipped during FOCI lookups (familyId non-null) - Backward compatible: old clients preserve the new 'ext' field via AdditionalFieldsJson round-trip; new clients handle missing 'ext' gracefully Tests: - 10 new unit tests covering cache key generation, FRT guard, serialization round-trip, backward compatibility, and integration with token acquisition - All existing AT partition, serialization, and FOCI tests continue to pass Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds refresh token (RT) cache partitioning aligned with the existing access-token partitioning model, enabling partitioned silent flows to locate the correct RT while preserving FRT (family refresh token) sharing semantics.
Changes:
- Extends
MsalRefreshTokenCacheItemto carry/serialize additional cache key components and incorporate them into the RT cache key. - Threads
requestParams.CacheKeyComponentsinto RT creation and adds RT-side filtering by additional key components during RT lookup (skipping for FRT lookups). - Adds new unit tests covering RT key hashing, (de)serialization behavior, and auth-code flow storage scenarios.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| tests/Microsoft.Identity.Test.Unit/CacheTests/RefreshTokenCachePartitionTests.cs | Adds unit tests validating RT partition hashing, serialization, and auth-code flow storage behaviors. |
| src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs | Passes cache-key components into RT creation and filters RT candidates by additional key components during lookup (skipping for FRT lookups). |
| src/client/Microsoft.Identity.Client/Cache/Items/MsalRefreshTokenCacheItem.cs | Adds RT partition components, cache-key suffixing, and ext serialization for RT partitioning (with FRT guard). |
Fix cache key casing by passing partition hash through GetCredentialKey's additionalKeys parameter so it gets lower-cased consistently. Fix iOS keychain collision by including partition hash in GetiOSService() for partitioned RTs. Rewrite tests with meaningful assertions: compare partitioned vs non-partitioned keys, simulate old MSAL by renaming ext field before deserialization, and validate lower-cased hash expectations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bgavrilMS
reviewed
Jun 19, 2026
Three new tests exercise the FindRefreshTokenAsync filter with mixed partitioned and non-partitioned RTs in cache: 1. Silent with partition key finds partitioned RT and refreshes 2. Silent without partition key does not find partitioned RT (MsalUiRequired) 3. Mixed cache: partition isolates the correct RT for each flow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Existing callers use WithCachePartitionKey for AT-only partition. Adding RT partition to the same API would break them, so RT partition is opt-in via a new bool parameter (default false). WithCachePartitionKey(key, value) remains AT-only (backward compat). WithCachePartitionKey(key, value, partitionRefreshToken: true) partitions both AT and RT. Adds a warning log when a non-partitioned silent call finds a partitioned RT, helping developers catch mismatched partition usage. Adds backward-compat test verifying silent calls without partitionRefreshToken still find partitioned RTs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The warning fires in the normal account transfer scenario where a partitioned RT coexists with a regular OIDC RT after session expiry. Not a misuse case, so the log would be noisy. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use |= so that once partitionRefreshToken is set to true by any WithCachePartitionKey call, a subsequent call without it cannot accidentally reset it to false. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Verifies that calling WithCachePartitionKey with partitionRefreshToken true followed by a call without it does not reset the flag. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ensures debug logs show the actual cache key including the partition hash, matching InitCacheKey and GetiOSService behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced Jun 27, 2026
Open
fix: Bump Microsoft.Identity.Client from 4.85.1 to 4.85.2
Halceyon/open-telemetry-trace-listener#204
Closed
Merged
Closed
Open
Open
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extends the existing access token (AT) cache partition mechanism (WithCachePartitionKey) to also partition refresh tokens (RTs). This allows callers who partition their AT cache to have matching RT isolation, enabling multi-resource token acquisition flows where a silent token request for a second resource can locate the RT from the first redemption via partition key.
Motivation
The AT partition feature (shipped in a previous release) allows callers to associate cache entries with an arbitrary partition key. However, refresh tokens were not partitioned, meaning that:
This change closes that gap.
Changes
MsalRefreshTokenCacheItem.csAdditionalCacheKeyComponentsproperty (SortedList<string, string>)cacheKeyComponentsparameterInitCacheKey()appends a SHA256 hash of components to the cache key (mirrors AT pattern)FromJObject/ToJObject: serialize components under theextJSON field (same key as AT)FamilyIdis set, partition components are silently ignored — Family Refresh Tokens are shared by design and must never be partitionedTokenCache.ITokenCacheInternal.csrequestParams.CacheKeyComponentsto the RT constructor inSaveTokenResponseAsyncFilterRefreshTokensByAdditionalKeyComponentsmethod (symmetric with the existing AT filter)FindRefreshTokenAsync— but skipped whenfamilyIdis non-null to preserve FOCI fallback behaviorBackward Compatibility
extfield is not consumed by older versions, so it lands inAdditionalFieldsJsonand survives round-trip serializationextfield results in null components — no partition filter applied, matching pre-existing behaviorTests
10 new unit tests in
RefreshTokenCachePartitionTests.cs:RTWithPartition_CacheKeyIncludesHashRTWithoutPartition_CacheKeyUnchangedFRT_NeverGetsPartitionedRTPartition_SerializationRoundTripRTPartition_SerializationWithoutComponentsextfield in JSONOldMsalReadsNewJson_PartitionFieldPreservedAsAdditionalFieldsextpreserved in round-tripNewMsalReadsOldJson_MissingPartitionFieldYieldsNullComponentsext→ null componentsFilterRefreshTokensByAdditionalKeyComponents_FiltersCorrectlyFilterRefreshTokensByAdditionalKeyComponents_NullComponents_KeepsAllAcquireTokenByAuthCode_WithPartition_StoresPartitionedRT_AsyncNo regressions: All existing AT partition tests (4), serialization tests (25+), and FOCI tests pass.