G Mac
G Mac

Reputation: 160

MSAL 2.x returns existing authentication result for a different B2C policy

UPDATE 1 2021-10-24

  1. We are using two different 'classic' sign-in/sign-up policies predating even MSAL 0.15. We don't use custom policies. I believe we can export them using the Manifest for the application, would that be useful? I would expect to redact identifiable information if I am posting here, but am not sure if that makes them useless for purpose...
  2. With respect to SSO, I think what @JasSuri-MSFT is saying is that because the claim set is the same/similar, the policy id itself is ignored (for SSO purposes)? Is this documented somewhere, such that we can get an idea of how different the claim sets need to be?
  3. Given that SSO behaviour, is there a way of getting the policy id itself to be used to discern SSO? This is why I made this initial post for MSAL 2.x because there might be something I am missing here in the configuration to restore previous behaviour.

INITIAL POST

We recently upgraded our SPAs from MSAL 0.x to 2.x, upgrading to use OAuth 2.0 Authorization Code Flow with PKCE in B2C. This is complete and working with 2.16.1, and we have tried upgrading to 2.18.0 without success to solve the below 'issue'.

As part of the upgrade work, we noticed that if an existing authentication result exists for the tenant, the login redirect is bypassed and we immediately handle the authentication process (AKA acquireTokenSilent()), which is great since if we just logged in and head to a different tab for the SPA we are logged in on that tab immediately.

However, we use different SPAs with different B2C policies in the same tenant, and we are unexpectedly getting the authentication result from one SPA policy for a different SPA policy (in the same tenant). Our application catches this and reports an error (since the claims in the access tokens are incompatible), but we are trying to understand what we can do to prevent this confusing situation.

For background, I will mea culpa here and say we were using MSAL 0.1.5 (!!). Under this version, each SPA authenticated separately, and each SPA could be logged into the same tenant simultaneously without interference (being attached to different domains).

With the issue in MSAL 2.x this is no longer the case, and it seems that authentication (specifically, claims) are 'leaking' between B2C policies. I have checked the msal.PublicClientApplication() for additional settings but see nothing. I do see a forceRefresh flag for acquireTokenSilent() but that indicates it is for network token check and switching it on has made no difference to getting cross policy authentication results.

What we do notice is that the authority included in the response (being passed thru via the handleRedirectPromise()) is the CORRECT policy id for the 2nd SPA. In addition, when we use acquireTokenSilent() the access token presented to our web server has the CORRECT policy id (in the tfp claim) of 2nd SPA, but the claims from that access token are for the 1st SPA's policy and authenticated user. Note we specifically validate the tvps.AuthenticationType for the 2nd SPA which is correct since the tfp was updated!

Here is the reproduction sequence (in abridged form):

  1. Open up SPA 1 login page on tab 1 (*)
  2. Get redirected to B2C page for authentication
  3. Redirect back to SPA 1 where authentication is completed by acquireTokenSilent() for SPA 1
  4. Web server validates SPA 1 access token claims for SPA 1
  5. Open up SPA 2 login page on tab 2 (*)
  6. Redirect back to SPA 2 where authentication is completed by acquireTokenSilent() for SPA 2
  7. Web server rejects SPA 1 access token claims for SPA 2 since the claims values are incompatible with the API call, but the tfp claim matches the 2nd SPA's policy id

(*) It appears that when the msal.PublicClientApplication()/loginRedirect() is performed, if there is a recent login the B2C login page redirect is bypassed and we immediately go into the handleRedirectPromise(). It seems this is where the authentication result from the old SPA 1 login gets passed back. Note that the response's authority IS correct for SPA 2.

Here is the format of the authority field in the msal.PublicClientApplication() config:

authority: `https://<ourtenant>.b2clogin.com/tfp/<ourtenant>.onmicrosoft.com/B2C_1_${policyId}`

where: policyId has the form of 'sign-up-in-spa' or 'sign-up-in-spa-phn'

So:

Upvotes: 0

Views: 985

Answers (1)

G Mac
G Mac

Reputation: 160

Update 2021-10-29

We ran into an issue with the Single sign-on-configuration set to Application. It seems that when logging out (and we are policy based) it left the SSO cookie in the tenant, and thus when going to sign-in again (after explicitly signing out) you were immediately signed in again (within the Web app session lifetime setting timeframe).

Changing this setting to Policy resolved the issue - signing out now signs out of the policy's sign-in.

INITIAL ANSWER

The answer is that this behaviour is defined in the B2C user flows for Sessions. It appears our very old use of MSAL 0.15 completely ignored this setting, probably (?) because we were using Implicit Flow. The more secure Authorization Code with PKCE we guess enabled enforcement.

Here is the Microsoft article about B2C session behaviour:

https://learn.microsoft.com/en-us/azure/active-directory-b2c/session-behavior?pivots=b2c-user-flow

In essence:

  • Navigate to Azure Portal
  • Select the desired B2C directory
  • Select the User flows menu
  • Select the SUSI (Sign-up/Sign-in) policy - ours are 'Sign up and sign in (Standard legacy)'
  • Select Properties
  • Under the Session behaviour section, change the setting from Tenant (which was ours) to:
    • Application - this appears to be at the application registration level. Signing out of a policy DOES NOT clear the SSO cookie - see above UPDATE
    • Policy - policy id based
    • Disabled

B2C Session Behaviour

Thanks @JasSuri-MSFT as your comment tickled my brain about session management and SSO.

Upvotes: 1

Related Questions