I'm writing a Blazor Server app that is using the Microsoft Identity Platform and MSAL to authenticate users against an azure active directory. I'm using .net 6
I'm trying to get the user profile from the Microsoft Graph API. I have a function for this that works most of the time. However, when MSAL wants to show the UI again (eg cached token expired, not available, or doesn't have the scopes) it all falls apart! :-(
The documented mechanism for this is that the API call throws a MsalUiRequiredException and that the calling code should catch this exception and delegate it to ConsentHandler.HandleException()
The problem is none that none of the documentation shows you how to carry on from there to recover. In my case, I try calling the graph API again and still get the same exception.
Here's my method:
private async Task<User> GetUserProfile()
{
try
{
return await GraphServiceClient.Me.Request().GetAsync();
}
catch (Exception ex)
{
ConsentHandler.HandleException(ex);
//What now?? - I still need to return a user!
//Try calling the graph service again?
return await GraphServiceClient.Me.Request().GetAsync(); //throws same exception again!
}
}
the exception I am getting is
Microsoft.Graph.ServiceException: Code: generalException
Message: An error occurred sending the request.
---> Microsoft.Identity.Web.MicrosoftIdentityWebChallengeUserException: IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent.
---> MSAL.NetCore.4.42.0.0.MsalUiRequiredException:
ErrorCode: user_null
Microsoft.Identity.Client.MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call.
at Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
...
The link in the error message explains the pattern I have used, but the example doesn't go on to complete its API call.
If the user refreshes the browser a few times the issue goes away (without showing the UI strangely) until the next time the token expires or I restart the service.
The question is: What should the catch block look like?
The takeaway from kavya's answer is that you have the let the exception bubble up to the top level and effectively abandon the the request after Calling ConsentHandler.HandleException(e) This allows the platform to redirect the browser to gather consent, and then redirect back again which restarts the original request. In blazor server my code looks like this:
//top level method in this blazor request
protected override async Task OnInitializedAsync()
{
try
{
//really function that eventually calls GetUserProfile.
var user = await GetUserProfile();
}
catch (Exception e)
{
ConsentHandler.HandleException(e);
throw; //exits OnInitializedAsync and allows the platform to take over and redirect.
}
//snip rest of this function
}
private async Task<User> GetUserProfile()
{
// no handling here let the exception bubble to the top of the stack
return await GraphServiceClient.Me.Request().GetAsync();
}
...
I tried to reproduce the scenario in my environment:
I have called below controller method :
MyController.cs
public async Task<IActionResult> Index()
{
var user = await _graphServiceClient.Me.Request().GetAsync();
ViewData["ApiResult"] = user.DisplayName;
return View();
}
Received same error:
MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call.
Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
MicrosoftIdentityWebChallengeUserException: IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent.
The cause was not mentioning the attribute [Authorize] on the controller method to authorize when using msal using Azure Active Directory:
Confirm and include the scopes you need during authorization and which are given in azure ad portal.After mentioning scopes in the [AuthorizeForScopes]
attribute:
[AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
public async Task<IActionResult> Index()
{
var user = await _graphServiceClient.Me.Request().GetAsync();
ViewData["ApiResult"] = user.DisplayName;
return View();
}
And allow MicrosoftIdentityWebChallengeUserException
to handle the exception in try-catch block while calling the user.
Exception handling:
try
{
// ITokenAcquisition.GetAccessTokenForUserAsync(...)
// return await GraphServiceClient.Me.Request().GetAsync();
await GraphServiceClient.Me.Request().GetAsync();
}
catch (MicrosoftIdentityWebChallengeUserException)
{
//MicrosoftIdentityWebChallengeUserException should bed re-throwed , so
// as to be caught [AuthorizeForScopes] exception handling attribute
// added to app Controller class.
throw;
}
catch (Exception ex)
{
exceptionMessage = ex.Message;
}
appsettings.json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "testxxxx.onmicrosoft.com",
"ClientId": "xxxx",
"TenantId": "xxx",
"ClientSecret": "xxxx",
"ClientCertificates": [
],
"CallbackPath": "/signin-oidc"
},
"DownstreamApi": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "https://graph.microsoft.com/.default"
}
Make sure to give api permissions
required to get user information. i.e; User.Read
and it has to be granted admin consent through portal or during authentication.
Then call to my API was successful:
Api :
You can check this c# - MsalUiRequiredException when calling Microsoft Graph SDK from NET Core web app - Stack Overflow to use exception handler middleware