'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 |
