[Bug] CIAM CUD fails to populate IAccount TenantProfiles property when using WithOidcAuthority
Library version used
4.61.3
.NET version
Net 8.0 MAUI- 8.0.300 MAUI - Android, iOS, Windows
Scenario
PublicClient - mobile app
Is this a new or an existing app?
This is a new app or experiment
Issue description and reproduction steps
Using the work described in #4654 on a public client the authentication works however attempting to use the built-in methods for evaluating the IAccount Identity Token properties from a cached account fails due to the AuthorityInfo that is generated when using WithOidcAuthority failing to enable the CanBeTenanted check which is evaluated here: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/4e51d0afea8aab1871e3a5fef7193b87ad43226d/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs#L1099-L1106
WithOidcAuthority uses the AuthorityInfo.FromGenericAuthority operation.
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/4e51d0afea8aab1871e3a5fef7193b87ad43226d/src/client/Microsoft.Identity.Client/AppConfig/PublicClientApplicationBuilder.cs#L233-L240
And AuthorityType.Generic isn't in the list for CanBeTenanted
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/4e51d0afea8aab1871e3a5fef7193b87ad43226d/src/client/Microsoft.Identity.Client/AppConfig/AuthorityInfo.cs#L124-L134
This may also include confidential clients, but I have not tested that workflow yet.
Relevant code snippets
redirectUri = $"msal{_configuration.GetValue<string>("ClientId")}://auth";
PublicClientApplicationBuilder.Create(_configuration.GetValue<string>("ClientId"))
.WithExperimentalFeatures()
.WithRedirectUri(redirectUri)
//.WithAuthority(_configuration.GetValue<string>("Authority")) //Works in non-CUD CIAM
.WithOidcAuthority(_configuration.GetValue<string>("Authority")) //Explicitly using WithOidcAuthority for supporting CIAM Custom Domains.
.WithLogging(_msalLogger, _configuration.GetValue<bool>("EnableLoggingPII"))
.Build()
Expected behavior
For the CIAM Authority, I expect that even with a custom domain, this would still work the same as a B2C tenant, where the authority is trusted to work throughout the library so long as it is put in the correct format.
Example for B2C: WithB2CAuthority("https://mylogin.mydomain.com/tfp/9f612e42-f354-4d76-a618-3e27c40e6a75/B2C_1_SUSI")
What I would expect with CIAM: WithCIAMAuthority("https://mylogin.mydomain.com/e43caeb4-9fcf-45c1-8fb2-5cbffcbce5e6").
This would then call out specifically that the authority is a CIAM tenant and that any CIAM-specific logic should be invoked and enforced with the understanding that it is on the developer to ensure that the CIAM authority is constructed properly.
Identity provider
Microsoft Entra External ID
Regression
No response
Solution and workarounds
Removed WithOidcAuthority configuration and fell back to WithAuthority removing the support for custom domains.
Reviewing the tests it doesn't look like there is consistent usage of the WithOidcAuthority vs the WithAuthority("<ciamcudauthority>",false) to understand the intent of which is supposed to be used.
Examples:
- https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/4e51d0afea8aab1871e3a5fef7193b87ad43226d/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/CiamIntegrationTests.cs#L107-L111
- https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/4e51d0afea8aab1871e3a5fef7193b87ad43226d/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/CiamIntegrationTests.cs#L153-L213 Neither of them check that the IAccount TenantProfiles is properly returned.
Tenant profiles are just parsed ID tokens.
In a work and school environment, you can have your home account in tenant A and be invited to tenant B. If you login to an app with both account A and account B, there will be 2 tenant profiles, corresponding to the 2 id tokens in the cache. Note that MSAL doesn't discover all tenants where your account is guest, it's strictly based on Id tokens.
Afaik this is not supported in CIAM world - i.e. you can invite work and school accounts as guests in the CIAM tenant, but that's about it. So there would always be a single tenant profile - i.e. 1 id token per user. And you can access that IdToken directly or parsed in the ClaimsPrincipal, properties of AuthenticationResult
My scenario is the member account type for home users of the CIAM tenant. The parsing of the ClaimsPrincipal works only on the first request to IDP because afterward if I need to get information from the Id token then it isn't accessible from IAccount out of the token cache because of how the Authority isn't declared as CanBeTenanted the ability to get any Id token out of the token cache to parse for properties of the user is blocked. Reaching into the token cache to get the current user works offline whereas attempting to send a request to ESTS would fail if there is no connection. This type of scenario can occur if the user opens the app on their mobile device without connectivity during which any parsing of user properties that would normally happen to get the user's permissions would need a durable location to pull from such as the token cache.
So you are interested in a mobile app. With public client, we expect you to call AcquireTokenSilent. This may or may not go to the network, i.e. if a valid access token is in the cache, MSAL will return it without going to the STS. AcquireTokenSilent returns an AuthenticationResult, which has the id token (raw and parsed in ClaimsPrincipal).
Did you expect to see token profiles via GetAccounts ?
Yes, I would expect to be able to access that via GetAccounts because the points in the application where I would be checking for the user properties are not tied to a particular scope that would used in a call to AcquireTokenSilent. Additionally, if access tokens are only valid for 1 hour but the user opens their app the next day and is offline then I would still want to be able to know who the user is and what they are permissioned to do inside the app even when offline using cached data.
Sample Code:
IAccount currentAccount = (await pca.GetAccounts()).FirstOrDefault();
IEnumerable<TenantProfile> tenantProfiles = currentAccount.GetTenantProfiles(); //This works with B2C in all cases and CIAM when not using custom domains based on the above-describeded authority configurations.
var profile = tenantProfiles.FirstOrDefault();
List<Claim> parsedClaims = MyClaimsMapper.ParseClaims(profile?.ClaimsPrincipal?.Claims)
//...do stuff with claims processed...
Current workaround is to call AcquireTokenSilent. You can use no scopes to ensure token cache hit.
Closing as workaround exists and the reported problem not being supported by CIAM.