Skip to content

AcquireTokenByAuthorizationCodeAsync should not return a token from the cache if the requested scopes are more than the scopes for the token contained in the cache #693

@jmprieur

Description

@jmprieur

Which Version of MSAL are you using ?*
MSAL.NET 2.4.1-preview

Which platform has the issue?
net4.5, netcore 2.1 (Web)

What authentication flow has the issue?

  • Desktop
    • Interactive
    • Integrated Windows Auth
    • Username / Password
    • Device code flow (browserless)
  • Mobile
    • Xamarin.iOS
    • Xamarin.Android
    • UWP
  • Web App
    • [ x] Authorization code
    • OBO
  • Web API
    • OBO
  • Daemon App
    • Client credentials

What is the identity provider ?

  • [ x] Azure AD
  • Azure AD B2C

Repro

  1. Get the code from https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2

  2. Checkout to branch jmprieur/addIncrementalConsent

  3. Comment the lines 121-125 in Extensions\TokenAcquisition.cs. These lines are a work around

    121     var account = await application.GetAccountAsync(context.Principal.GetMsalAccountId());
    122     if (account!=null)
    123     {
    124       await application.RemoveAsync(account);
    125     }
    126     var result = await application.AcquireTokenByAuthorizationCodeAsync(context.ProtocolMessage.Code, scopes.Except(scopesRequestedByMsalNet));
1. Run the Web site under the debugger
1. sign-in
1. Select **Contact**
   => you should see information about youself (from the graph)
1. Put a breakpoint in the HomeController.Contact() method
   ```CSharp
   public async Task<IActionResult> Contact()
   {
    var scopes = new string[] { "user.read" };
     . . .
  1. In the web site click on contact again.
  2. Now in edit and continue mode, change the scope to "Mail.Send"
  3. Continue the debugger

Expected behavior
If the code above is not commented, you'll notice the step-up experience. you are asked for incremental consent (for Mail.Send) and the Contact page is displayed again.

Actual behavior

with the workaround code commented, AcquireTokenByAuthorizationCodeAsync returns from the cache a token that does not have all the scopes, and therefore you have an infinite loop of selecting your account (once you have consented once) because the HomeController.Contact() method attempts to get a token with the new scopes, fails, sends a challenge to get a token with the new scopes, the user selects the account/consents if needed, and then the code is redeemed, but since AcquireTokenByAuthorizationCodeAsync returns the previous token, it does not contain the required claim (Mail.Send), and therefore when controller.Contact() is called, the same thing happens forever until the user is tired ...

Possible Solution

I don't think that AcquireTokenByAuthorizationCodeAsync should even hit the cache. If developers really want to attempt the cache they should call AcquireTokenSilent before AcquireTokenByAuthorizationCodeAsync but I don't see for which scenario.

** Work around for the moment **

remove the account before calling AcquireTokenByAuthorizationCodeAsync

 var account = await application.GetAccountAsync(context.Principal.GetMsalAccountId());
 if (account!=null)
 {
  await application.RemoveAsync(account);
 }
 var result = await application.AcquireTokenByAuthorizationCodeAsync(context.ProtocolMessage.Code, scopes.Except(scopesRequestedByMsalNet));

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions