Microsoft-authentication-library-for-dotnet: [Bug] Unable to get access token for multiple scopes

Created on 18 Sep 2019  路  7Comments  路  Source: AzureAD/microsoft-authentication-library-for-dotnet

Which Version of MSAL are you using ?
MSAL 4.3.1

Platform
.NET Core 2.2

What authentication flow has the issue?

  • Desktop / Mobile

    • [ ] Interactive

    • [ ] Integrated Windows Auth

    • [ ] Username Password

    • [ ] Device code flow (browserless)

  • Web App

    • [ ] Authorization code

    • [ ] OBO

  • Web API

    • [x] OBO

Other? - please describe;

Is this a new or existing app?
This is a new app or experiment that I'm doing by modifying the following sample project.
https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2/tree/master/2.%20Web%20API%20now%20calls%20Microsoft%20Graph

I would like to get access token OBO for Graph API and Dynamics CRM API by providing scopes together and getting access token back with multiple audiences, so that it can be used for both of them.

Repro

  1. Add API Permission on TodoListService-v2 for Dynamics CRM user_impersonation
  2. Make sure that it also has Microsoft Graph User.Read
  3. In TodoListController in TodoListService project, add Dynamics CRM user_impersonation scope to line 90:
    string[] scopes = { "https://graph.microsoft.com//User.Read", "https://admin.services.crm.dynamics.com/user_impersonation" };
public async Task<string> CallGraphApiOnBehalfOfUser()
        {
            string[] scopes = { "https://graph.microsoft.com/User.Read", "https://admin.services.crm.dynamics.com/user_impersonation" };

            // we use MSAL.NET to get a token to call the API On Behalf Of the current user
            try
            {
                string accessToken = await _tokenAcquisition.GetAccessTokenOnBehalfOfUserAsync(scopes);
                dynamic me = await CallGraphApiOnBehalfOfUser(accessToken);
                return me.userPrincipalName;
            }
            catch (MsalUiRequiredException ex)
            {
                _tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeader(scopes, ex);
                return string.Empty;
            }
        }

Expected behavior
Access token should be returned with multiple audiences.

Actual behavior
Access token returned is only for audience for which the scope is mentioned first in the scopes array.

Possible Solution

Additional context/ Logs / Screenshots
I've tried many combinations but none of them work:
"user.read" "user_impersonation"
"user.read user_impersonation"
"https://graph.microsoft.com//User.Read", "https://admin.services.crm.dynamics.com//user_impersonation"
Tried double forward slashes, single slashes, nothing worked.

I've also tried with Postman and it has the same behavior, in that it returns the access token with audience only for the scope that is mentioned first.

answered question

Most helpful comment

Thanks for the link!

In general, multiple audience scenarios are a security risk, which is why we don't currently expose a way of building it. The main concern here, that Ping leaves out, is that you must trust that every API and micro service receiving a multi audience will never be compromised. If they're compromised and get a token that can be used to call another audience, they can - and now your data is at risk, and possibly your business if the token has write permissions.

We are working to propose an update to the JWT standards to build tokens that can safely be issued with multiple audiences - but that adds complexity on the client side.

At this time, the secure (and only) option is to get multiple tokens for multiple audiences. This is handled by the libraries including refreshing and caching.

All 7 comments

@mohsinonxrm : as you have noticed with Postman, Azure AD does not provide scopes for two resources, only the first one. The way to go is to do two calls (one to get the scopes for the first audience, and the second to get the scopes from the second audience), this will work (and the token should accumulate the scope, I'd think. Something like this:

public async Task<string> CallGraphApiOnBehalfOfUser()
        {
            string[] scopesOfDifferentAudiances= { "https://graph.microsoft.com/User.Read", "https://admin.services.crm.dynamics.com/user_impersonation" };

            // we use MSAL.NET to get a token to call the API On Behalf Of the current user
            try
            {
                string accessToken;

            foreach(string resource in  scopesOfDifferentAudiances)
            { 
             accessToken = await _tokenAcquisition.GetAccessTokenOnBehalfOfUserAsync(scopes);
            } 
               dynamic me = await CallGraphApiOnBehalfOfUser(accessToken);
                return me.userPrincipalName;
            }
            catch (MsalUiRequiredException ex)
            {
                _tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeader(scopes, ex);
                return string.Empty;
            }
        }

See also https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-net-user-gets-consent-for-multiple-resources

@jmprieur , understood. I also reviewed the article you mentioned. Is this something that MSAL will allow in the future? Also, what's the best practice when it comes to scenario like this?

@mohsinonxrm : this is not a limitation of MSAL, but of Azure AD. But I'll inquire if there are plans to change that.
The best practice is to get the tokens with the right scopes just before calling the API.

Thanks for the link!

In general, multiple audience scenarios are a security risk, which is why we don't currently expose a way of building it. The main concern here, that Ping leaves out, is that you must trust that every API and micro service receiving a multi audience will never be compromised. If they're compromised and get a token that can be used to call another audience, they can - and now your data is at risk, and possibly your business if the token has write permissions.

We are working to propose an update to the JWT standards to build tokens that can safely be issued with multiple audiences - but that adds complexity on the client side.

At this time, the secure (and only) option is to get multiple tokens for multiple audiences. This is handled by the libraries including refreshing and caching.

@hpsin , thanks for the explanation. This helps.

@mohsinonxrm closing the question as Hirsch answered. Feel free to reopen if you disagree.

Was this page helpful?
0 / 5 - 0 ratings