question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Bug] Inconsistent results using OBO flow

See original GitHub issue

Logs and network traces Without logs or traces, it is unlikely that the team can investigate your issue. Capturing logs and network traces is described in Logging wiki.

Which version of MSAL.NET are you using? Microsoft.Identity.Client 4.34.0 Microsoft.Identity.Web 1.14.0

Platform .Net Framework 4.7.2

What authentication flow has the issue?

  • Mobile
    • Interactive
    • Integrated Windows Authentication
    • Username Password
    • Device code flow (browserless)
  • Web app
    • Authorization code
    • On-Behalf-Of
  • Daemon app
    • Service to Service calls

Other?

  • Sign in with mobile app
  • Call private API which creates confidential client application and calls AquireTokenOnBehalfOf
  • Call downstream graph API (outlook)

Is this a new or existing app? a. The app is in production, and I have upgraded to a new version of MSAL.

Repro

    public class MicrosoftService : IMicrosoftService
    {
        public IConfidentialClientApplication ConfidentialClientApplication { get; set; }
        private readonly ICompanyUserRepo _userRepo;
        private readonly IAuthenticationSettings _authenticationSettings;
        private readonly IDatabaseSettings _databaseSettings;

        public MicrosoftService(
            ICompanyUserRepo userRepo,
            IAuthenticationSettings configuration,
            IDatabaseSettings databaseSettings)
        {
            _userRepo = userRepo;
            _authenticationSettings = configuration;
            _databaseSettings = databaseSettings;

            ConfidentialClientApplication = BuildApp();
        }

        public async Task GetAccessTokenAndUpdateUser(string userId, IEnumerable<string> scopes, string jwt)
        {
            await AquireTokenOnBehalfOf(scopes, jwt);

            var user = _userRepo.GetByIdAndPartitionKeyOrDefault(userId, userId);
            user.MicrosoftIdentity = jwt;

            _userRepo.Update(user);
        }

        public async Task<AuthenticationResult> GetToken(string userId, IEnumerable<string> scopes)
        {
            var user = _userRepo.GetByIdAndPartitionKeyOrDefault(userId, userId);
            var jwt = user.MicrosoftIdentity;

            return await AquireTokenOnBehalfOf(scopes, jwt);
        }

        public async Task RevokeToken(string userId, IEnumerable<string> scopes)
        {
            var user = _userRepo.GetByIdAndPartitionKeyOrDefault(userId, userId);
            var authResult = await AquireTokenOnBehalfOf(scopes, user.MicrosoftIdentity);

            await ConfidentialClientApplication.RemoveAsync(authResult.Account);
            user.MicrosoftIdentity = null;

            _userRepo.Update(user);
        }

        private async Task<AuthenticationResult> AquireTokenOnBehalfOf(IEnumerable<string> scopes, string jwt)
        {
            var userAssertion = new UserAssertion(jwt);
            var res = await ConfidentialClientApplication.AcquireTokenOnBehalfOf(scopes, userAssertion).ExecuteAsync();

            return res;
        }

        private IConfidentialClientApplication BuildApp()
        {
            // The application which retreives the auth token must also use the same client id and redirect url when requesting the token. The auth token from the response is then passed to the AquireTokenByAuthorisationCode method below. 

            IConfidentialClientApplication app = ConfidentialClientApplicationBuilder
                .Create(_authenticationSettings.MicrosoftRevokeAppId)
                .WithRedirectUri(_authenticationSettings.EmailScanWebAppBaseUrl + _authenticationSettings.MicrosoftRedirectAction)
                .WithClientSecret(_authenticationSettings.MicrosoftRevokeAppSecret)
                .WithTenantId(_authenticationSettings.MicrosoftTenantId)
                .Build();

            IMsalTokenCacheProvider cosmosTokenCacheProvider = CreateCosmosTokenCacheSerialiser();
            cosmosTokenCacheProvider.Initialize(app.UserTokenCache);

            return app;
        }

        private IMsalTokenCacheProvider CreateCosmosTokenCacheSerialiser()
        {
            IServiceCollection services = new ServiceCollection().AddLogging();
            var cosmosConnectionString = string.Format("AccountEndpoint={0};AccountKey={1};", _databaseSettings.RevokeGlobalDatabaseUri, _databaseSettings.RevokeGlobalDatabaseKey);

            services.AddDistributedTokenCaches();
            services.AddCosmosCache((CosmosCacheOptions cacheOptions) =>
            {
                cacheOptions.DatabaseName = _databaseSettings.RevokeGlobalDatabase;
                cacheOptions.ContainerName = _authenticationSettings.CosmosMsalContainer;
                cacheOptions.CreateIfNotExists = true;
                cacheOptions.ClientBuilder = new CosmosClientBuilder(cosmosConnectionString);
            });

            IServiceProvider serviceProvider = services.BuildServiceProvider();
            IMsalTokenCacheProvider msalTokenCacheProvider = serviceProvider.GetRequiredService<IMsalTokenCacheProvider>();

            return msalTokenCacheProvider;
        }
    }
}

Expected behavior A token is retrieved which can be used to call the downstream api

Actual behavior Sometimes the token is retrieved, sometimes it returns this error instead:

AADSTS70000: The request was denied because one or more scopes requested are unauthorized or expired. The user must first sign in and grant the client application access to the requested scope.\r\nTrace ID: 19a4f1a4-09cd-4729-8ccb-b1299cc37600\r\nCorrelation ID: 34dc13fe-7c6e-49af-acdc-e85b62088927\r\nTimestamp: 2021-07-15 13:37:24Z

Possible solution

Additional context / logs / screenshots Add any other context about the problem here, such as logs and screenshots.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:35 (15 by maintainers)

github_iconTop GitHub Comments

2reactions
bgavrilMScommented, Jul 26, 2021

There may be a different way consent and actual tokens are handled for Mail.Read between AAD (work and school) and MSA (personal) accounts. I’ll try to repro.

1reaction
jmprieurcommented, Jul 19, 2021

@SirElTomato @JoshB82 the scopes look good. In fact MSAL.NET always requests “openid profile offline_access” so that the cache works correctly, so you could use only “mail.read” if you wish.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Getting error AADST50013 for OBO flow using Azure AD ...
I.e. Users tokens issued from Azure AD token endpoint, and performing OBO against the same endpoint with the users AAD token. AAD endpoint: ......
Read more >
Implement the On Behalf Of flow between an Azure AD ...
This article shows how to implement the On Behalf Of flow between two APIs, one using Azure AD to authorize the HTTP requests...
Read more >
Microsoft identity platform and OAuth 2.0 On-Behalf-Of flow
This article describes how to use HTTP messages to implement service to service authentication using the OAuth2.0 On-Behalf-Of flow.
Read more >
Time of Event on Calendar Is Incorrect After Flow
Solved: Dear All We have a Flow that is triggered after an event is added on an Exchange calendar which updates the event....
Read more >
Just what *is* the /.default scope in the Microsoft identity ...
when using the on-behalf-of (OBO) flow, where our API is making calls on behalf of the user to a different API; something like...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found