This article shows how to force a phishing resistant authentication for an ASP.NET Core application using Azure AD and a conditional access policy which forces a phishing resistant authentication using a conditional access authentication context. The ASP.NET Core application forces this by requiring the acrs claim in the id_token with the value of c4 which is the authentication context created for this in this tenant.
Code https://github.com/damienbod/AspNetCoreAzureADCAE
The following five steps are required to set this up. I used Microsoft Entra for this but Graph can be used as well which makes automatization and infrastructure as code (IaC) deployments possible.
- Step 1 Authentication method definition
- Step 2 Create an CA Auth context
- Step 3 Create a policy to use the Azure AD CA Auth context
- Step 4 Define the Azure App registration
- Step 5 Use inside an ASP.NET Core application
Step 1 Authentication method definition
Using https://entra.microsoft.com, you can create a custom authentication method, or re-use some of the given standard definitions. A phishing resistant authentication method is already defined in the portal. You can also create a custom method that for example only accepts your company specific FIDO2 keys.

Creating a specific authentication method allows you to choose a specific authentication method. The phishing resistant default definition is a pre-defined authentication method.

Step 2 Create an CA Auth context
An authentication context needs to be created to force a policy in the ASP.NET Core application. Create a new authentication context using the Microsoft Entra.

Add the naming of the policy and the authentication context identifier. I used c4 for my phishing resistant requirement.

You could also create the authentication context using Microsoft Graph. I created the CaeAdministrationTool application in the demo repo showing how to implement this.
Step 3 Create a policy to use the Azure AD CA Auth context
You can create the conditional access policy to use the authentication context and force the authentication method as requires. Create a new policy.

Select the created authentication context in the Cloud apps or actions.

In the access controls, grant access to users that authenticate with a phishing resistant authentication.

Using this policy, a user must authentication with one of the phishing resistant methods.
Step 4 Define the Azure App registration
The ASP.NET Core application requires an Azure App registration with the correct configuration to authenticate with Azure AD and the use conditional access policy. The application must validate the CA and not a downstream API. This means we need to validate this in the id_token. This is done using the xms_cc claim. You can add this in the App registration manifest.
"optionalClaims": {
"idToken": [
{
"name": "xms_cc",
"source": null,
"essential": false,
"additionalProperties": []
}
],
"accessToken": [],
"saml2Token": []
},
Microsoft.Identity.Web is used to implement the authentication for Azure AD. The app.settings requires the ClientCapabilities value with the cp1 value.
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "damienbodsharepoint.onmicrosoft.com",
"TenantId": "5698af84-5720-4ff0-bdc3-9d9195314244",
"ClientId": "daffd2e8-3718-4ac4-b971-c7f1bb570375",
"CallbackPath": "/signin-oidc",
"ClientCapabilities": [ "cp1" ]
//"ClientSecret": "--in-user-secrets-or-key-vault--"
},
You can check some of the standard settings required for the Web application Azure App registration using the manifest definition. The docs for this can be found on the Microsoft.Identity.Web Github repo Wiki.
"oauth2AllowIdTokenImplicitFlow": true,
"optionalClaims": {
"idToken": [
{
"name": "xms_cc",
"source": null,
"essential": false,
"additionalProperties": []
}
],
"accessToken": [],
"saml2Token": []
},
"replyUrlsWithType": [
{
"url": "https://localhost:44414/signin-oidc",
"type": "Web"
}
],
"signInAudience": "AzureADMyOrg",
Step 5 Use inside an ASP.NET Core application
The application must validate if the id_token and the corresponding claims identity has the acrs claim with the value c4 which represents the authentication context created for the phishing resistant conditional access policy. If this is configured correctly, a phishing resistant authentication is required to use the application.
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using System;
using System.Linq;
namespace RazorCaePhishingResistant;
/// <summary>
/// Claims challenges, claims requests, and client capabilities
///
/// https://docs.microsoft.com/en-us/azure/active-directory/develop/claims-challenge
///
/// Applications that use enhanced security features like Continuous Access Evaluation (CAE)
/// and Conditional Access authentication context must be prepared to handle claims challenges.
/// </summary>
public class CaeClaimsChallengeService
{
private readonly IConfiguration _configuration;
public CaeClaimsChallengeService(IConfiguration configuration)
{
_configuration = configuration;
}
public string? CheckForRequiredAuthContextIdToken(string authContextId, HttpContext context)
{
if (!string.IsNullOrEmpty(authContextId))
{
string authenticationContextClassReferencesClaim = "acrs";
if (context == null || context.User == null || context.User.Claims == null || !context.User.Claims.Any())
{
throw new ArgumentNullException(nameof(context), "No Usercontext is available to pick claims from");
}
var acrsClaim = context.User.FindAll(authenticationContextClassReferencesClaim).FirstOrDefault(x => x.Value == authContextId);
if (acrsClaim?.Value != authContextId)
{
string clientId = _configuration.GetSection("AzureAd").GetSection("ClientId").Value;
var cae = "{\"id_token\":{\"acrs\":{\"essential\":true,\"value\":\"" + authContextId + "\"}}}";
return cae;
}
}
return null;
}
}
The admin page requires a strong authentication. If this is missing, a challenge is returned informing the identity provider (AAD) which claim is required to use the application. This is specified in the OpenID Connect shared signal and events specifications.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
namespace RazorCaePhishingResistant.Pages;
public class AdminModel : PageModel
{
private readonly CaeClaimsChallengeService _caeClaimsChallengeService;
public AdminModel( CaeClaimsChallengeService caeClaimsChallengeService)
{
_caeClaimsChallengeService = caeClaimsChallengeService;
}
[BindProperty]
public IEnumerable<string>? Data { get; private set; }
public IActionResult OnGet()
{
// if CAE claim missing in id token, the required claims challenge is returned
// C4 is used in the phishing resistant policy
var claimsChallenge = _caeClaimsChallengeService
.CheckForRequiredAuthContextIdToken(AuthContextId.C4, HttpContext);
if (claimsChallenge != null)
{
var properties = new AuthenticationProperties { RedirectUri = "/admin" };
properties.Items["claims"] = claimsChallenge;
return Challenge(properties);
}
Data = new List<string>()
{
"Admin data 1",
"Admin data 2"
};
return Page();
}
}
Now the admin page of the application requires the correct authentication. You could force this for the complete application or just a single page of the app. If the user does not authenticate using a phishing resistant authentication or cannot use this, the application page cannot be used. This is really useful when deploying administration applications to tenants which do not enforce this for all users.
Notes
This works really well and you can force a phishing resistant authentication from the application. MFA using the authenticator app will not work and the users of of Azure AD and applications using Azure AD have more protection.
Links
https://entra.microsoft.com/#view/Microsoft_AAD_IAM/AuthenticationMethodsMenuBlade/~/AuthStrengths
https://danielchronlund.com/2022/01/07/the-attackers-guide-to-azure-ad-conditional-access/
https://cloudbrothers.info/en/azure-attack-paths/
[…] Force phishing resistant authentication in an ASP.NET Core application using Azure AD (Damien Bowden) […]