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?
Update
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();
}
...