Home > front end >  How to use MS Graph in a Action Filter
How to use MS Graph in a Action Filter

Time:01-05

I am trying to run an action filter any time one of my controllers is called from my front end to check if a token issued by Azure AD is still active. If I run this code in one of my controllers methods directly the code is fine and does as follows:

  1. If token is expired or cache has been cleared, MS Graph SDK returns a redirect to MS Login
  2. If token is valid it runs the MS Graph API and returns the results as normal.

In this ActionFilter below if the scenario is 1 the code just stops and errors out. In visual studio it actually generates the following service error

    {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.46.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)
   at Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
   at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken)
   at Microsoft.Identity.Client.ApiConfig.Executors.ClientApplicationBaseExecutor.ExecuteAsync(AcquireTokenCommonParameters commonParameters, AcquireTokenSilentParameters silentParameters, CancellationToken cancellationToken)
   at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForWebAppWithAccountFromCacheAsync(IConfidentialClientApplication application, ClaimsPrincipal claimsPrincipal, IEnumerable`1 scopes, String tenantId, MergedOptions mergedOptions, String userFlow, TokenAcquisitionOptions tokenAcquisitionOptions)
   at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable`1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
    StatusCode: 0 
    ResponseBody:  
    Headers: 
   --- End of inner exception stack trace ---
   at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable`1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
   at Microsoft.Identity.Web.TokenAcquisitionAuthenticationProvider.AuthenticateRequestAsync(HttpRequestMessage request)
   at Microsoft.Graph.AuthenticationHandler.SendAsync(HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Microsoft.Graph.HttpProvider.SendRequestAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)}

So basically the ActionFilter does not automatically redirect the user to the MS Login screen or generate any response to send back to the front end for that matter. It just errors out even with a try and catch statement. So is there any way to do this? My action filter is set as follows:

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Graph;
using Microsoft.Identity.Client;
using Microsoft.Identity.Web;
using Microsoft.Extensions.DependencyInjection;
using System;

public class CheckTokenFilter : IActionFilter
{

private readonly GraphServiceClient _graphServiceClient;
private readonly ITokenAcquisition _tokenAcquisition;
private readonly string[] initialScopes;
private readonly MicrosoftIdentityConsentAndConditionalAccessHandler _consentHandler;

public CheckTokenFilter(GraphServiceClient graphServiceClient, ITokenAcquisition tokenAcquisition, IConfiguration configuration, MicrosoftIdentityConsentAndConditionalAccessHandler consentHandler)
{
    _graphServiceClient = graphServiceClient;
    _tokenAcquisition = tokenAcquisition;
    initialScopes = configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
    this._consentHandler = consentHandler;
}

public void OnActionExecuted(ActionExecutedContext context)
{
    //noop
}

public async void OnActionExecuting(ActionExecutingContext context)
{
    User currentUser = null;

    try
    {
        currentUser = await _graphServiceClient.Me.Request().GetAsync();
    }
    // Catch CAE exception from Graph SDK
    catch (ServiceException svcex) when (svcex.Message.Contains("Continuous access evaluation resulted in claims challenge"))
    {
        try
        {
            Console.WriteLine($"{svcex}");
            string claimChallenge = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(svcex.ResponseHeaders);
            _consentHandler.ChallengeUser(initialScopes, claimChallenge);
        }
        catch (Exception ex2)
        {
            _consentHandler.HandleException(ex2);
        }
    }
}

}

Current method that is working based on answer below:

    public class CheckTokenFilter : IActionFilter
{

    private readonly GraphServiceClient _graphServiceClient;
    private string SignedInRedirectUri;

    public CheckTokenFilter(GraphServiceClient graphServiceClient, IConfiguration configuration)
    {
        _graphServiceClient = graphServiceClient;
        SignedInRedirectUri = configuration["AzureAd:SignedInRedirectUri"];
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        //noop
    }

    public async void OnActionExecuting(ActionExecutingContext context)
    {
        //First we check if token is valid and if not it will generate an error and challenge user causing a redirect which is caught in our program.cs. It sends a 403 error to the front end to load the login page.
        User currentUser = null;

        try
        {
            currentUser = await _graphServiceClient.Me.Request().GetAsync();
        }
        // Catch CAE exception from Graph SDK
        catch
        {
            context.Result = new ChallengeResult(OpenIdConnectDefaults.AuthenticationScheme,
            new AuthenticationProperties
            {
                IsPersistent = true,
                RedirectUri = SignedInRedirectUri
            });
        }
    }
}

CodePudding user response:

What about using something like:

context.Result = new ChallengeResult(Your Scheme)

I use something similar in my solution, but I use:

    [Authorize]
    [ServiceFilter(typeof(*******AuthenticationFilter))]

This way if the token is not valid, it will go off and challenge the user before it goes to the filter. The filter can then do additional work once the user is authenticated at a high level. The filter just throws an exception in my case, as it shouldn't get into a bad state.

  • Related