'Refresh access_token using AcquireTokenSilent

I have a asp.netcore MVC application which passes access_token to a downstream API.

After lot of struggle I figured out how to get the access_token using MSAL.Net. But the access token expires every hour, so I had to refresh it. So I am using the AcquireTokenSilent which accepts account as a parameter.

My Startup.cs

public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });

        services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
                options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })
            .AddCookie(options =>
            {
                options.Events= new CookieAuthenticationEvents()
                {
                    OnValidatePrincipal = OnValidatePrincipal
                };
            })
            .AddOpenIdConnect(options =>
            {
                options.Authority = "https://login.microsoftonline.com/{tenantId}/v2.0";
                options.ClientId = Configuration["AzureAD:ClientId"];
                options.ClientSecret = Configuration["AzureAD:ClientSecret"];
                options.CallbackPath = Configuration["AzureAD:CallbackPath"];
                options.ResponseType = "code id_token";
                options.Scope.Add("openid");
                options.Scope.Add("profile");
                options.Scope.Add("offline_access");
                options.Scope.Add("api://{ClientidOfAPI}/user_impersonation");
                options.SaveTokens = true;
                options.Events= new OpenIdConnectEvents()
                {
                    OnAuthorizationCodeReceived = OnAuthorizationCodeReceived
                };
            });

        services.AddMvc(options =>
        {
            var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
            options.Filters.Add(new AuthorizeFilter(policy));
        })
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 
       
    }
     

     private async Task OnValidatePrincipal(CookieValidatePrincipalContext context)
     {

         var application = GetOrBuildConfidentialClientApplication(context.HttpContext, context.Principal);
         var accounts = await application.GetAccountsAsync();
         
        AuthenticationResult result = await application.AcquireTokenSilent(
            new []{ "openid", "profile", "offline_access", "api://{ClientidOfAPI}/user_impersonation" },
            accounts.FirstOrDefault()).ExecuteAsync();
        
    }


     private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
    {
        var application = GetOrBuildConfidentialClientApplication(context.HttpContext, context.Principal);

        var result = await application.AcquireTokenByAuthorizationCode(new [] { "api://{ClientidOfAPI}/user_impersonation" }, context.ProtocolMessage.Code)
            .ExecuteAsync();

        context.HandleCodeRedemption(result.AccessToken, result.IdToken);
    }
    private IConfidentialClientApplication GetOrBuildConfidentialClientApplication(HttpContext httpContext, ClaimsPrincipal claimsPrincipal)
    {
        var request = httpContext.Request;
        string currentUri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, "/signin-oidc" ?? string.Empty);
        string authority = $"https://login.microsoftonline.com/{tenantid}/v2.0";

        var app = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(new ConfidentialClientApplicationOptions()
            {
                ClientId = "Clientid of Client"
                ClientSecret = "password of Client"
        })
            .WithRedirectUri(currentUri)
            .WithAuthority(authority)
            .Build();
        return app;
    }

    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
           app.UseHsts();
        }
        ...

        app.UseAuthentication();

        app.UseMvc();
    }

But the OnValidatePrincipal which calls the AcquireTokenSilent method, cannot get the accounts and the accounts is returned empty.

var accounts = await application.GetAccountsAsync();

Not sure why I am getting an empty list for accounts. Is there a better way to refresh the access_token?

Update:

I have changed the Startup.cs a bit. I have removed the OnValidatePrinciple () from Startup and added that functionality to AquireTokenSilently() to Controller action. Still the same result.

Do I need to implement caching to get value out of GetAccountsAsync()? I am not doing caching manualy as far as I am aware how would I do it?



Solution 1:[1]

When you can't get the token silently you need to get it interactively.

According to the documentation you should do this with a try...catch pattern like this:

var accounts = await application.GetAccountsAsync();
AuthenticationResult result;
try
{
    result = await application.AcquireTokenSilent(
        new []{ "openid", "profile", "offline_access", "api://{ClientidOfAPI}/user_impersonation" },
        accounts.FirstOrDefault()).ExecuteAsync();
}
catch (MsalUiRequiredException)
{
    result = await application.AcquireTokenInteractive(
        new []{ "openid", "profile", "offline_access", "api://{ClientidOfAPI}/user_impersonation" })
        .ExecuteAsync();
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 HackSlash