0

I have a .NET MVC Core 3.1 Webapp running on azure. This webapp is with SSO against Azure AD and is consumming powerbi API and graph API in delegated mode.

All was working fine but now I regularly have failed_to_acquire_token_silently Exceptions when AcquireTokenSilentAsync is triggered. This is not 100% of the times and happears to me a bit randomly.

Let me try to extract what I think are the most relevant code parts.

Startup.cs / ConfigureServices:

            services.AddAuthentication("Azures").AddPolicyScheme("Azures", "Authorize AzureAd or AzureAdBearer", options =>
            {
                options.ForwardDefaultSelector = context =>
                {
....
                };
            })

                 .AddJwtBearer(x =>
                 {
                     .....

                 })
                 // For browser access
                .AddAzureAD(options => Configuration.Bind("AzureAd", options));

Startup.cs / ConfigureTokenHandling:

       private void ConfigureTokenHandling(IServiceCollection services)
        {
            if (Configuration["AuthWithAppSecret:ClientSecret"] != "")
            {
                services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
                {
                    options.ResponseType = Configuration["AuthWithAppSecret:ResponseType"];
                    options.ClientSecret = Configuration["AuthWithAppSecret:ClientSecret"];


                    options.Events = new OpenIdConnectEvents
                    {
                        OnAuthorizationCodeReceived = async ctx =>
                        {

                            HttpRequest request = ctx.HttpContext.Request;
                            //We need to also specify the redirect URL used
                            string currentUri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path);
                            //Credentials for app itself
                            var credential = new ClientCredential(ctx.Options.ClientId, ctx.Options.ClientSecret);

                            //Construct token cache
                            ITokenCacheFactory cacheFactory = ctx.HttpContext.RequestServices.GetRequiredService<ITokenCacheFactory>();
                            TokenCache cache = cacheFactory.CreateForUser(ctx.Principal);

                            var authContext = new AuthenticationContext(ctx.Options.Authority, cache);


                            string resource = Configuration["PowerBI:PowerBiResourceUrl"];
                            AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
                                ctx.ProtocolMessage.Code, new Uri(currentUri), credential, resource);



                            //Tell the OIDC middleware we got the tokens, it doesn't need to do anything
                            ctx.HandleCodeRedemption(result.AccessToken, result.IdToken);
                        }

                    };
                });

            }
        }

A controller is like this :

    public class ProjectsController : BaseController
    {
        private readonly ITokenCacheFactory _tokenCacheFactory;


        public ProjectsController(MyContext context,  IConfiguration configuration, ITokenCacheFactory tokenCacheFactory)
        {
            _context = context;
            _tokenCacheFactory = tokenCacheFactory;
            _configuration = configuration;

        }

Later triggered by the controller:

       static public async Task<string> GetAccessTokenAsync2(IConfiguration _configuration, ITokenCacheFactory _tokenCacheFactory, ClaimsPrincipal User, string resURL, Uri redirectURI)
        {

            string authority = _configuration["AzureAd:Authority"];
            string clientId = _configuration["AzureAd:ClientId"];
            string clientSecret = _configuration["AuthWithAppSecret:ClientSecret"];


            var cache = _tokenCacheFactory.CreateForUser(User);
            var authContext = new AuthenticationContext(authority, cache);


            var credential = new ClientCredential(clientId, clientSecret);
            var userId = User.GetObjectId();


            AuthenticationResult result;
            try
            {
                result = await authContext.AcquireTokenSilentAsync(
                    resURL,
                    credential,
                    new UserIdentifier(userId, UserIdentifierType.UniqueId));
            }
            catch (AdalException ex)
            {
                mylog.Info("GetAccessTokenAsync - Adal Ex:" + ex.ErrorCode);

                if (ex.ErrorCode == "failed_to_acquire_token_silently")
                {

                    // There are no tokens in the cache. 
                    try
                    {
                        PlatformParameters param = new PlatformParameters();

                        result = await authContext.AcquireTokenAsync(resURL, clientId, redirectURI, param, new UserIdentifier(userId, UserIdentifierType.UniqueId));
                    }
                    catch (Exception e)
                    {
                        mylog.Error("GetAccessTokenAsync - AcquireTokenAsync" + e.ToString());
                        throw e;
                    }
                    

                }
                else
                    throw ex;
            }



            return result.AccessToken;
        }

AcquireTokenAsync has been added to turn around the failed_to_acquire_token_silently issue (but it is totaly failling).

Do you have any idea why it is failing from time to time ? Any other idea how to fix it ?

Thanks!!! Christian

EDIT 07/04: Here an example:

2021-04-07 15:18:24.674 +00:00 OnAuthorizationCodeReceived is triggered for user fd918ddf-fbb9-40d2-812b-b01876118f42
2021-04-07 15:18:31.675 +00:00  AcquireTokenSilentAsync - trigger exception userId 'fd918ddf-fbb9-40d2-812b-b01876118f42'

The users is authenticated against AD correctly. A code is received and few seconds later there a failed_to_acquire_token_silently exception raised.

chu
  • 9
  • 2
  • I see that you're using ADAL, as you aware ADAL is getting deprecated, so i would suggest you to migrate your code from ADAL to MSAL. MSAL is now the recommended authentication library for use with the Microsoft identity platform. You may want to [get it started from here](https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-migration). – Dev Apr 04 '21 at 12:21
  • Thanks. I'll take a look to it. – chu Apr 07 '21 at 16:48

1 Answers1

0

The error failed_to_acquire_token_silently occurs when an access token cannot be found in the cache or the access token is expired.

Code sample here:

// STS
string cloud = "https://login.microsoftonline.com";
string tenantId = "331e6716-26e8-4651-b323-2563936b416e";
string authority = $"{cloud}/{tenantId}";

// Application
string clientId = "65b27a1c-693c-44bf-bf92-c49e408ccc70";
Uri redirectUri = new Uri("https://TodoListClient");

// Application ID of the Resource (could also be the Resource URI)
string resource = "eab51d24-076e-44ee-bcf0-c2dce7577a6a";

AuthenticationContext ac = new AuthenticationContext(authority);
AuthenticationResult result=null;
try
{
 result = await ac.AcquireTokenSilentAsync(resource, clientId);
}
catch (AdalException adalException)
{
 if (adalException.ErrorCode == AdalError.FailedToAcquireTokenSilently
     || adalException.ErrorCode == AdalError.InteractionRequired)
  {
   result = await ac.AcquireTokenAsync(resource, clientId, redirectUri,
                                       new PlatformParameters(PromptBehavior.Auto));
  }
}

Note that, AcquireTokenSilent does not need to be called in the Client credentials flow (when the application acquires token without a user, but in its own name)

But you use client credentials flow in your code, you could get access token via AcquireTokenAsync.

clientCredential = new ClientCredential(clientId, appKey);

AuthenticationContext authenticationContext =
  new AuthenticationContext("https://login.microsoftonline.com/<tenantId>");
   AuthenticationResult result = 
      await authenticationContext.AcquireTokenAsync("https://resourceUrl",
                                                    clientCredential);
unknown
  • 6,778
  • 1
  • 5
  • 14
  • If my reply is helpful, please accept it as answer(click on the mark option beside the reply to toggle it from greyed out to fill in.), see https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work – unknown Apr 20 '21 at 08:30
  • Thanks @Pamela for you answer. I'm requesting token during the client credentials flow because I'm using this event to trigger automatic actions based on Microsoft Graph API. I have other location where I'm requesting Tokens as well. I'm currently moving to MSAL – chu May 04 '21 at 11:02
  • I found out the issue. The AD is configured with MFA but strangely MFA is not always triggered during authentication. Therefore when you request a token even with a code, the request is refused. The way to fix it is to force MFA on the oauth2/authorize request. Here some explanations : https://stackoverflow.com/questions/51785256/how-to-make-oauth2-work-for-azure-active-directory-with-multi-factor-authenticat/51824130#51824130 – chu May 07 '21 at 19:14