MFA On behalf Flow Failing https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof-ca

100 Views Asked by At

I'm testing the following code and have set up the required app registrations in Azure: https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof-ca

I expect to be asked for MFA login when calling the downstream API.

It seems to be working apart from the fact it isn't triggering the MFA when entering the SignInCA class in the todolistclient/mainwindow.cs code after the downstream API has sent a insufficient claims response back. I think there's an issue with the 'claims' parameter passed into:

result = await _app.AcquireTokenInteractive(scopes)
                   .WithClaims(claims).ExecuteAsync(); // Line 419 todolistclient/mainwindow.cs

I get a claims parameter of :

{
  "Code": "insufficient_claims",
  "Message": "AADSTS50079: Due to a configuration change made by your administrator, or because you moved to a new location, you must enroll in multi-factor authentication to access '34e7281b-b8c1-4279-b1d0-c381be0407bd'.\r\nTrace ID: b7820969-c380-4bb0-8898-f5c78b613500\r\nCorrelation ID: 471204f7-6413-436c-891e-e67c305ba9a6\r\nTimestamp: 2023-09-12 11:31:19Z",
  "AdditionalInfo": null,
  "InnerError": {
    "Date": "2023-09-12T11:31:20.6293785Z",
    "RequestId": "3f89cc00-61web-40a0-9c6e-f07ae5749bf0",
    "ClientRequestId": "3f89c120-6eab-40a0-9c6e-f07ae5749bf0"
  }
}

I'm expecting that to trigger an MFA UI screen but it isn't. Is this the message that is expected back from the insufficient claims response? I can't find any documentation around what the claims should look like to trigger MFA.

Thanks.

1

There are 1 best solutions below

0
Rukmini On

Note that: To trigger MFA you need to configure conditional access policy to enable MFA for the Azure AD Application.

Go to Azure Active Directory -> Security -> Conditional Access -> Create new policy

enter image description here

Otherwise, you can set the Authentication Context to trigger the MFA. First, set the Authentication Context in Azure Portal:

enter image description here

For sample,

private async Task<Dictionary<string, string>> GetAuthenticationContextValues()
{
     Dictionary<string, string> dictACRValues = new Dictionary<string, string>()
        {
                {"C1","Require strong authentication" },
                {"C2","Require compliant devices" },
                {"C3","Require trusted locations" }
    };

And then check for the required auth:

public void CheckForRequiredAuthContext(string method)
{
    string authType = _commonDBContext.AuthContext.FirstOrDefault(x => x.Operation == method && x.TenantId == _configuration["AzureAD:TenantId"])?.AuthContextId;

    if (!string.IsNullOrEmpty(authType))
    {
        HttpContext context = this.HttpContext;

        string authenticationContextClassReferencesClaim = "acrs";

        if (context == null || context.User == null || context.User.Claims == null || !context.User.Claims.Any())
        {
            throw new ArgumentNullException("No Usercontext is available to pick claims from");
        }

        Claim acrsClaim = context.User.FindAll(authenticationContextClassReferencesClaim).FirstOrDefault(x => x.Value == authType);

        if (acrsClaim == null || acrsClaim.Value != authType)
        {
            string clientId = _configuration.GetSection("AzureAd").GetSection("ClientId").Value;
            var base64str = Convert.ToBase64String(Encoding.UTF8.GetBytes("{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"" + authType + "\"}}}"));

            context.Response.Headers.Append("WWW-Authenticate", $"Bearer realm=\"\", authorization_uri=\"https://login.microsoftonline.com/common/oauth2/authorize\", client_id=\"" + clientId + "\", error=\"insufficient_claims\", claims=\"" + base64str + "\", cc_type=\"authcontext\"");
            context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            string message = string.Format(CultureInfo.InvariantCulture, "The presented access tokens had insufficient claims. Please request for claims requested in the WWW-Authentication header and try again.");
            context.Response.WriteAsync(message);
            context.Response.CompleteAsync();
            throw new UnauthorizedAccessException(message);
        }
    }
}
    public bool IsClientCapableofClaimsChallenge(HttpContext context)
    {
        string clientCapabilitiesClaim = "xms_cc";

        if (context == null || context.User == null || context.User.Claims == null || !context.User.Claims.Any())
        {
            throw new ArgumentNullException("No Usercontext is available to pick claims from");
        }

        Claim ccClaim = context.User.FindAll(clientCapabilitiesClaim).FirstOrDefault(x => x.Type == "xms_cc");

        if (ccClaim != null && ccClaim.Value == "cp1")
        {
            return true;
        }

        return false;
    }

For full code implementation check this GitHub Blog by kalyankrishna1.

After configuring the MFA policy, I am able to get the MFA screen like below:

enter image description here

After setting up the MFA, the user redirected to the redirect page:

enter image description here

I generated access token using OBO flow via Postman:

https://login.microsoftonline.com/TenantID/oauth2/v2.0/token

client_id:ClientID
client_secret:ClientSecret
grant_type:urn:ietf:params:oauth:grant-type:jwt-bearer
assertion:access_token
requested_token_use:on_behalf_of
scope:api://ClientID/.default

enter image description here

References:

Conditional Access authentication context now in public preview - Microsoft Community Hub by Alex Simons

Developer guidance for Azure AD Conditional Access authentication context - Microsoft Entra