microsoft-authentication-library-for-dotnet icon indicating copy to clipboard operation
microsoft-authentication-library-for-dotnet copied to clipboard

[Bug] CIAM CUD fails to populate IAccount TenantProfiles property when using WithOidcAuthority

Open OmnipotentOwl opened this issue 1 year ago • 5 comments

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.

OmnipotentOwl avatar Jun 14 '24 22:06 OmnipotentOwl

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

bgavrilMS avatar Jun 17 '24 09:06 bgavrilMS

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.

OmnipotentOwl avatar Jun 17 '24 13:06 OmnipotentOwl

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 ?

bgavrilMS avatar Jun 17 '24 15:06 bgavrilMS

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...

OmnipotentOwl avatar Jun 17 '24 15:06 OmnipotentOwl

Current workaround is to call AcquireTokenSilent. You can use no scopes to ensure token cache hit.

bgavrilMS avatar Jun 17 '24 16:06 bgavrilMS

Closing as workaround exists and the reported problem not being supported by CIAM.

iulico-1 avatar Jul 02 '24 16:07 iulico-1