Force MFA in Blazor using Azure AD and Continuous Access

This article shows how to force MFA from your application using Azure AD and a continuous access auth context. When producing software which can be deployed to multiple tenants, instead of hoping IT admins configure this correctly in their tenants, you can now force this from the application. Many tenants do not force MFA.

Code: https://github.com/damienbod/AspNetCoreAzureADCAE

Blogs in this series

Steps to implement

  1. Create an authentication context in Azure for the tenant (using Microsoft Graph).
  2. Add a CA policy which uses the authentication context.
  3. Implement an authentication challenge using the claims challenge in the Blazor WASM.

Creating a conditional access authentication context

A continuous access (CA) authentication context was created using Microsoft Graph and a policy was created to use this. See the first blog in this series for details on setting this up.

Force MFA in the Blazor application

Now that the continuous access (CA) authentication context is setup and a policy is created requiring MFA, the application can check that the required acrs with the correct value is present in the id_token. We do this is two places, in the login of the account controller and in the OpenID Connect event sending the authorize request. The account controller Login method can be used to set the claims parameter with the required acrs value. By requesting this, the Azure AD policy auth context is forced.

[HttpGet("Login")]
public ActionResult Login(string? returnUrl, 
	string? claimsChallenge)
{
  // var claims = 
  //  "{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"c1\"}}}";
  // var claims = 
  //  "{\"id_token\":{\"acrs\":{\"essential\":true,\"value\":\"c1\"}}}";
  var redirectUri = !string.IsNullOrEmpty(returnUrl) ? returnUrl : "/";

  var properties = new AuthenticationProperties { RedirectUri = redirectUri };

  if(claimsChallenge != null) 
  {
	string jsonString = claimsChallenge.Replace("\\", "")
		.Trim(new char[1] { '"' });

	properties.Items["claims"] = jsonString;
  }
  else
  {
	// lets force MFA using CAE for all sign in requests.
	properties.Items["claims"] 
	  = "{\"id_token\":{\"acrs\":{\"essential\":true,\"value\":\"c1\"}}}";
  }

  return Challenge(properties);
}

In the application an ASP.NET Core authorization policy can be implemented to force the MFA. All requests require a claim type acrs with the value c1, which we created in the Azure tenant using Microsoft Graph.

services.AddMicrosoftIdentityWebAppAuthentication(configuration)
    .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
    .AddMicrosoftGraph("https://graph.microsoft.com/v1.0", scopes)
    .AddInMemoryTokenCaches();

services.AddAuthorization(options =>
{
    options.AddPolicy("ca-mfa", policy =>
    {
        policy.RequireClaim("acrs", AuthContextId.C1);
    });
});

By using the account controller login method, only the login request forces the auth context. If the context needs to be forced everywhere, then a middleware using the OnRedirectToIdentityProvider event can be used to add the extra request parameter on every OIDC authorize request. The OnRedirectToIdentityProvider event can be used to add this to all requests, which has not already added the claims parameter. You could also only use this without the login implementation in the account controller.

services.AddRazorPages().AddMvcOptions(options =>
{
    var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .RequireClaim("acrs", AuthContextId.C1)
        .Build();
    options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();

services.Configure<MicrosoftIdentityOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.Events.OnRedirectToIdentityProvider = context =>
    {
        if(!context.ProtocolMessage.Parameters.ContainsKey("claims"))
        {
            context.ProtocolMessage.SetParameter(
            "claims",
            "{\"id_token\":{\"acrs\":{\"essential\":true,\"value\":\"c1\"}}}");
        }

        return Task.FromResult(0);
    };
});

Now all requests require the auth context which is used to require the CA MFA policy.

Links

https://github.com/damienbod/Blazor.BFF.AzureAD.Template

https://github.com/Azure-Samples/ms-identity-ca-auth-context

https://github.com/Azure-Samples/ms-identity-dotnetcore-ca-auth-context-app

https://docs.microsoft.com/en-us/azure/active-directory/conditional-access/overview

https://github.com/Azure-Samples/ms-identity-dotnetcore-daemon-graph-cae

https://docs.microsoft.com/en-us/azure/active-directory/develop/developer-guide-conditional-access-authentication-context

https://docs.microsoft.com/en-us/azure/active-directory/develop/claims-challenge

https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-conditional-access-dev-guide

https://techcommunity.microsoft.com/t5/itops-talk-blog/deep-dive-how-does-conditional-access-block-legacy/ba-p/3265345

4 comments

  1. […] Force MFA in Blazor using Azure AD and Continuous Access (Damien Bowden) […]

  2. […] Force MFA in Blazor using Azure AD and Continuous Access – Damien Bowden […]

  3. Douglas Fontans · · Reply

    Hi Damien,

    Please, could you accept my invite on LinkedIn? It’s would be amazing.
    So, do you have any article or tutorial about blazor and keycloak? I looking for, but don’t found.
    I’m from tax area, but I want to build a app, for this I have been studying blazor for last year.

    Yours contribution are excellent, congrats!
    Regards,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: