Azure-Samples / active-directory-aspnetcore-webapp-openidconnect-v2

An ASP.NET Core Web App which lets sign-in users (including in your org, many orgs, orgs + personal accounts, sovereign clouds) and call Web APIs (including Microsoft Graph)
MIT License
1.38k stars 992 forks source link

Code example does not handle OData error to process the CAE challenge from Microsoft Graph. #754

Open JakubHromada opened 9 months ago

JakubHromada commented 9 months ago

Microsoft.Identity.Web version

2.16.1

Web app sign-in

Not applicable

Web API (call Graph or downstream APIs)

2-WebApp-graph-user/2-1-Call-MSGraph

Deploy to Azure

Not applicable

Auth Z

Not applicable

Description

The process to handle CAE challenges from MS Graph by catching a ServiceException doesn't work. Upon revoking user session the GraphServiceClient now returns ODataError exception with the requested claims. The code example is not handling this type of exception.

Please update the code example to handle the OData exception to process the CAE challenge from Microsoft Graph.

Reproduction steps

  1. User signs in to web app
  2. Admin revokes all sessions for user in Entra Id
  3. User tries to access Profile page

Error message

ODataError: Continuous access evaluation resulted in challenge with result: InteractionRequired and code: TokenIssuedBeforeRevocationTimestamp

Id Web logs

No response

Relevant code snippets

[AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
public async Task<IActionResult> Profile()
{
    User currentUser = null;

    try
    {
        currentUser = await _graphServiceClient.Me.GetAsync();
    }
    // Catch CAE exception from Graph SDK - This is not ServiceException anymore, the correct exception to catch is ODataError
    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(_graphScopes, claimChallenge);
            return new EmptyResult();
        }
        catch (Exception ex2)
        {
            _consentHandler.HandleException(ex2);
        }
    }

    try
    {
        // Get user photo
        using (var photoStream = await _graphServiceClient.Me.Photo.Content.GetAsync())
        {
            byte[] photoByte = ((MemoryStream)photoStream).ToArray();
            ViewData["Photo"] = Convert.ToBase64String(photoByte);
        }
    }
    catch (Exception pex)
    {
        Console.WriteLine($"{pex.Message}");
        ViewData["Photo"] = null;
    }

    ViewData["Me"] = currentUser;
    return View();
}

Regression

No response

Expected behavior

Process the CAE challenge from Microsoft Graph.

anaugust113 commented 7 months ago

using Microsoft.Graph.Models.ODataErrors;

To solve the issue, you can update the Profile() function in HomeController.cs to: public async Task Profile() { User currentUser = null;

try
{
    currentUser = await _graphServiceClient.Me.GetAsync();
}
// Catch CAE exception from Graph SDK
catch (ODataError svcex) when (svcex.Message.Contains("Continuous access evaluation resulted in challenge"))
//catch (ServiceException svcex) when (svcex.Message.Contains("Continuous access evaluation resulted in challenge"))
{
    try
    {

        // Assuming svcex.ResponseHeaders is of type IDictionary<string, IEnumerable<string>>
        HttpResponseHeaders httpResponseHeaders = new HttpResponseMessage().Headers;
        foreach (var header in svcex.ResponseHeaders)
        {
            string headerName = header.Key;
            foreach (var headerValue in header.Value)
            {
                httpResponseHeaders.TryAddWithoutValidation(headerName, headerValue);
            }
        }

        Console.WriteLine($"{svcex}");
        string claimChallenge = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(httpResponseHeaders);
        _consentHandler.ChallengeUser(_graphScopes, claimChallenge);
        return new EmptyResult();
    }
    catch (Exception ex2)
    {
        _consentHandler.HandleException(ex2);
    }
}

try
{
    // Get user photo
    using (var photoStream = await _graphServiceClient.Me.Photo.Content.GetAsync())
    {
        byte[] photoByte = ((MemoryStream)photoStream).ToArray();
        ViewData["Photo"] = Convert.ToBase64String(photoByte);
    }
}
catch (Exception pex)
{
    Console.WriteLine($"{pex.Message}");
    ViewData["Photo"] = null;
}

ViewData["Me"] = currentUser;
return View();

}