Which version of Microsoft Identity Web are you using?
Microsoft Identity Web 0.2.3-preview
Where is the issue?
Is this a new or an existing app?
a. The app is in production and I have upgraded to a new version of Microsoft Identity Web.
Repro
I'm trying to upgrade one of the projects I'm working on to use the Microsoft.Identity.Web nuget package. So far really working well but I'm having trouble figuring out how to add additional claims which I was previously doing by the following:
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Events.OnAuthorizationCodeReceived = async ctx =>
{
var serviceProvider = services.BuildServiceProvider();
var distributedCache = serviceProvider.GetRequiredService<IDistributedCache>();
var identifier = ctx.Principal.FindFirst(ObjectIdentifierType)?.Value;
var cca = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(new ConfidentialClientApplicationOptions()
{
ClientId = "ClientId",
RedirectUri = "RedirectUri",
ClientSecret = "ClientSecret"
})
.WithAuthority(ctx.Options.Authority)
.Build();
var tokenCache = new SessionTokenCache(identifier, distributedCache);
tokenCache.Initialize(cca.UserTokenCache);
var token = await cca.AcquireTokenByAuthorizationCode(scopes, ctx.TokenEndpointRequest.Code).ExecuteAsync();
ctx.HandleCodeRedemption(token.AccessToken, token.IdToken);
// get the claims
var claimService = serviceProvider.GetRequiredService<ClaimService>();
var response = await apiClient.GetUserAdditionalClaimsAsync(token.AccessToken);
// add the claims
};
Now when I try to use the ITokenAcquisition.GetAccessTokenForUserAsync() method instead of using the ConfidentialClientApplicationBuilder
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftWebApp(options =>
{
Configuration.Bind("AzureAD", options);
options.Events.OnAuthorizationCodeReceived = async ctx =>
{
var serviceProvider = services.BuildServiceProvider();
var tokenAcquisition = serviceProvider.GetRequiredService<ITokenAcquisition>();
var token = await tokenAcquisition.GetAccessTokenForUserAsync("scopes");
// get the claims
var claimService = serviceProvider.GetRequiredService<ClaimService>();
var response = await apiClient.GetUserAdditionalClaimsAsync(token);
// add the claims
};
})
.AddMicrosoftWebAppCallsWebApi(Configuration, new[] { "scopes" })
.AddDistributedTokenCaches();
Any help would be so much appreciated on how best to handle this.
Thanks!
@mia01 : did you see this article? https://github.com/AzureAD/microsoft-identity-web/wiki/Managing-incremental-consent-and-conditional-access
@mia01
Would the following work for you?
services.AddMicrosoftWebApp(Configuration)
.AddMicrosoftWebAppCallsWebApi(Configuration, new string[] { "scopes", "you", "need" ))
.AddMemoryTokenCaches();
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
var previousEventHandler = options.Events.OnAuthorizationCodeReceived;
options.Events.OnAuthorizationCodeReceived = async ctx =>
{
// Ensure the Auth code is processed and the cache populated
await previousEventHandler(ctx);
// Get the token acquisition service here
var tokenAcquistion = serviceProvider.GetRequiredService<ITokenAcquisition>();
var token = await tokenAcquisition.GetAccessTokenForUserAsync("scopes");
}
}
@jmprieur thanks for that snippet. Looks like it could work IIUC!
I'll give it a go...
@jmprieur I've tried this yet still getting the exact same error, unfortunately :(
I can see that the cache is populated with the token but still the call fails
@mia01 : I'm trying to understand your scenario. Why do you want to acquire a token from the Startup.cs, and not from the controller actions / Razor/Blazor pages ?
I'm also having the same issue/error and have similar use case. I want to make a call to Graph service to retrieve user membership so that I can add Group names as claims.
@sameer-kumar I see from this issue that you issue was resolved related to this?
@mia01 : I'm trying to understand your scenario. Why do you want to acquire a token from the Startup.cs, and not from the controller actions / Razor/Blazor pages ?
@jmprieur we are trying to add a bunch of custom claims to the user. We want to add this once when user is authenticated. We determine what claims the user by calling Graph Api which means we need the token at that point.
@sameer-kumar I'm interested what was the code that worked for you?
@pmaytak yes, my use case is solved.
@mia01 I just followed along this with my custom modifications specific to my use case and it works for me.
Closing as it's answered.
See also this code (and the called method) which does it: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/blob/b07a9e06206f7274fdcadc34a50b8bebf9666fcf/5-WebApp-AuthZ/5-2-Groups/Startup.cs#L51
Feel free to reopen if you disagree
We'd like to be able to centralise the configuration of our HttpClient in Startup.cs. So instead of
services.AddHttpClient<IMyService, MyService>();
We are moving to something like this,
services.AddHttpClient(nameof(MyService), (hc) =>
{
string apiBaseAddress = this.configuration["MyApi:BaseAddress"];
hc.BaseAddress = new Uri(apiBaseAddress);
hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
});
That works fine but if we wanted to move the following in there too
var accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(new[] { this.configuration["ClinicianApi:Scopes"] });
hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
We'd need access to ITokenAcquisition in Startup. There are a few references that suggest calling
var serviceProvider = services.BuildServiceProvider();
var tokenAcquisition = serviceProvider.GetRequiredService<ITokenAcquisition>()
However if you do that you get a warning when you build, "Avoid calls to BuildServiceProvider in ConfigureServices"
Therefore we would still like to know "How can I call ITokenAcquisition.GetAccessTokenForUserAsync from startup.cs"
Updating the proposed solution: @mia01 @munkii
services.AddMicrosoftWebApp(Configuration)
.AddMicrosoftWebAppCallsWebApi(Configuration, new string[] { "scopes", "you", "need" ))
.AddMemoryTokenCaches();
services.AddMicrosoftIdentityConsentHandler();
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
var previousEventHandler = options.Events.OnAuthorizationCodeReceived;
options.Events.OnAuthorizationCodeReceived = async ctx =>
{
// Ensure the Auth code is processed and the cache populated
await previousEventHandler(ctx);
// Get the token acquisition service here
var tokenAcquistion = serviceProvider.GetRequiredService<ITokenAcquisition>();
var consentHandler = serviceProvider.GetRequiredService<MicrosoftIdentityConsentAndConditionalAccessHandler>();
try
{
var token = await tokenAcquisition.GetAccessTokenForUserAsync("scopes");
}
catch (Exception ex)
{
consentHandler .HandleException(ex);
}
}
}