Add Federated Managed Identity (FMI) support for client credentials flow#1025
Conversation
neha-bhargava
left a comment
There was a problem hiding this comment.
Some minor comments otherwise looks good
| * This is the single source of truth for the fmi_path cache key hash computation, | ||
| * used by both cache writes (TokenCache) and cache reads (silent lookup). | ||
| */ | ||
| String computeFmiCacheKeyHash() { |
There was a problem hiding this comment.
Should this be abstracted to some method in case some other additional key needs to be added
There was a problem hiding this comment.
Could you explain what you mean a bit more? This is an internal method which validates the FMI path isn't blank and calls the more generic computeExtCacheKeyHash that actually makes the hash for the cache key, so there's not a whole lot that can be abstracted.
There was a problem hiding this comment.
Basically can compute cache key with any other additional parameter that requires to be included in cache key
There was a problem hiding this comment.
I see what you mean now, good catch: the agent definitely made this part too specific.
In the latest commit I've refactored it to be more generic and expandable.
There was a problem hiding this comment.
Pull request overview
Adds Federated Managed Identity (FMI) support to MSAL Java's client credentials flow. A new fmiPath parameter on ClientCredentialParameters is injected as the fmi_path body parameter on the token POST, and cached tokens are isolated by an ext_cache_key_hash (SHA-256 over sorted key/value components, Base64URL-encoded) under a new ACCESS_TOKEN_EXTENDED credential type. A new AssertionRequestOptions context object and a Function<AssertionRequestOptions, String> overload of ClientCredentialFactory.createFromCallback let assertion callbacks see the per-request fmiPath, clientId, and tokenEndpoint.
Changes:
- New public APIs:
ClientCredentialParameters.fmiPath(...),AssertionRequestOptions,ClientCredentialFactory.createFromCallback(Function<AssertionRequestOptions,String>). - Cache isolation via
ext_cache_key_hashandACCESS_TOKEN_EXTENDEDcredential type plumbed throughAccessTokenCacheEntity,TokenCache,SilentRequest, andAcquireTokenByClientCredentialSupplier. - New unit tests (
FmiTest.java) and integration tests (FmiIT.java,AgenticIT.java) plus aTestHelper.getInstanceDiscoveryResponse()helper.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
ClientCredentialParameters.java |
Adds fmiPath field/builder and computeFmiCacheKeyHash() helper. |
ClientCredentialRequest.java |
Injects fmi_path into the OAuth POST body. |
ClientCredentialFactory.java |
New createFromCallback(Function<AssertionRequestOptions,String>) overload. |
ClientAssertion.java |
New context-aware constructor, assertion(options), isContextAware(). |
AssertionRequestOptions.java |
New context object with clientId, tokenEndpoint, clientAssertionFmiPath. |
TokenRequestExecutor.java |
Passes AssertionRequestOptions to context-aware assertions. |
CredentialTypeEnum.java |
Adds ACCESS_TOKEN_EXTENDED("AText"). |
AccessTokenCacheEntity.java |
Adds extCacheKeyHash field, getter/setter, key-suffix, JSON round-trip. |
TokenCache.java |
Computes/persists extCacheKeyHash on write; filters on read. |
SilentRequest.java |
Carries extCacheKeyHash through silent-lookup path. |
AcquireTokenSilentSupplier.java |
Forwards extCacheKeyHash to cache lookup. |
AcquireTokenByClientCredentialSupplier.java |
Propagates FMI hash to the silent request. |
StringHelper.java |
New computeExtCacheKeyHash(SortedMap) (SHA-256 + Base64URL). |
test/.../FmiTest.java |
Unit tests for fmi_path, cache key isolation, hash, callback context. |
test/.../TestHelper.java |
Adds getInstanceDiscoveryResponse() helper. |
integrationtest/.../FmiIT.java |
Real-Entra integration tests for FMI flows. |
integrationtest/.../AgenticIT.java |
Agent-identity callback-context and cache-isolation IT. |
This PR adds Federated Managed Identity (FMI) support to MSAL Java's client credentials flow, enabling Leg 1 and Leg 2 of the agent identity protocol. FMI allows a blueprint application to acquire tokens scoped to a specific agent identity by providing an
fmi_pathparameter in the token request.What's included
New public APIs:
ClientCredentialParameters.fmiPath(String)— sets thefmi_pathbody parameter sent in the client credentials POST request. Rejects null, empty, and whitespace values (matching MSAL .NET'sWithFmiPathvalidation).ClientCredentialFactory.createFromCallback(Function<AssertionRequestOptions, String>)— creates a client assertion from a context-aware callback function (needed for Leg 2, where the assertion callback must know the FMI path to acquire the correct credential)AssertionRequestOptions— immutable context object passed to assertion callbacks, providingclientId(),tokenEndpoint(), andclientAssertionFmiPath()Cache key isolation (
ext_cache_key):fmi_pathvalues are isolated in the cache using an extended cache key hashClientCredentialParametersinstance (immutable after construction), matching MSAL .NET's approach of computing once at cache item creation"AccessToken"to"AText"(AccessToken_Extended) for tokens with additional cache key componentsInternal changes:
ClientCredentialRequestinjectsfmi_pathinto the OAuth2 POST body when setTokenRequestExecutorpassesclientAssertionFmiPathto the assertion callback viaAssertionRequestOptionsClientAssertionextended to supportFunction<AssertionRequestOptions, String>callbacks (in addition to existing staticStringandCallable<String>modes). Dispatch priority: context-aware Function > Callable > static string.TokenCacheextended withextCacheKeyHashMatches()bidirectional filter logicAccessTokenCacheEntityextended withextCacheKeyHashfieldStringHelper.computeExtCacheKeyHash()implements the SHA-256 hash algorithmAcquireTokenByClientCredentialSupplierpropagatesextCacheKeyHashthrough the silent request pathTests:
FmiTest.java— 19 unit tests covering: input validation (null/empty/blank fmiPath rejection), cache key computation and cross-SDK hash verification, assertion callback context propagation (clientAssertionFmiPathpassed correctly), bidirectional cache isolation (FMI vs non-FMI, different FMI paths), and legacyCallablefallback behaviorFmiIT.java— 7 integration tests covering FMI credential acquisition (from cert, from RMA, chained FMI credentials, token from FMI credential, multi-FMI-path cache isolation)AgenticIT.java— 3 integration tests covering FMI scenarios using RMA (FMI credential acquisition from RMA, token cache hit for repeated requests)Alignment with MSAL .NET
This implementation matches the FMI behavior available on MSAL .NET's
mainbranch. Key equivalences:WithFmiPath(string)ClientCredentialParameters.fmiPath(String)WithClientAssertion(Func<AssertionRequestOptions, Task<string>>)ClientCredentialFactory.createFromCallback(Function<AssertionRequestOptions, String>)AssertionRequestOptions(ClientId, TokenEndpoint, FmiPath)AssertionRequestOptions(clientId, tokenEndpoint, clientAssertionFmiPath)CoreHelpers.ComputeAccessTokenExtCacheKey()StringHelper.computeExtCacheKeyHash()"ATExt"credential type"AText"credential type (case differs but cache key is lowercased for lookups in both SDKs)InitCacheKey)ClientCredentialParametersstring.IsNullOrWhiteSpacevalidation onWithFmiPathParameterValidationUtils.validateNotBlankonfmiPath()builder setterKnown differences from .NET (intentional)
AssertionRequestOptionsexposesclientId,tokenEndpoint,clientAssertionFmiPath. .NET additionally exposesTenantId,Authority,Claims,ClientCapabilities,CorrelationId. The additional fields can be added in a future PR if needed.WithFmiPathForClientAssertion(a separate FMI path passed only to the assertion callback, distinct from the body parameter). This is infrastructure for the compositeAcquireTokenForAgentAPI which will be added in a subsequent PR.CancellationTokensemantics. The callback isFunction<AssertionRequestOptions, String>(synchronous) rather than .NET's asyncFunc<..., CancellationToken, Task<string>>.AssertionResponse/ClientSignedAssertionsupport (assertion + token-binding certificate for mTLS PoP). This can be added in a future PR when jwt-pop scenarios are needed.