If your app does many things that have a normal security level but does one thing that is very sensitive, like a payment transfer, then the simplest option is to require MFA when you sign into the app. However, if the sensitive operation is only carried out by some users, and also infrequently, the high security bar for everyone all the time isn’t going to make you the popular guy at the company’s X-mas party.
There is an option to get your dance card full at the X-mas party, and that is to use Entra Authentication Context together with Conditional Access policies. It will be a just-in-time step-up MFA when the sensitive operation is carried out. Using Authentication context requires that the code base is built for it. It is not something you just can enforce by configuring it in the Entra portal.
Authentication Context sequence diagram
Below is a sequence diagram describing what is happening when Authentication Context comes into play. Everything is normal until step 4 where the webapp makes a call to the webapi for the sensitive operation. In step 5, the webapi checks if conditions for executing the sensitive operation is met, and when it is not, the webapi responds with a 401 Unauthorized. But it doesn’t stop there, because it also tells the webapp why it is unauthorized and what the webapp has to do to resolve the situation as it responds with “you are missing the value cX in the acr claim”. This “unauthorized dance” is OIDC protocol standard and in the Entra case, acquiring the cX value in the acr claim is performed by a Conditional Access policy. The dance continues between step 5 through 14 until the webapp has an access token that is acceptable to perform the sensitive operation, which is carried out in step 15.

Configuring Authentication Context and a Conditional Access policy to act on it
Creating an Authentication context is pretty unexciting. You give it a name (which has no real meaning more than distinguishing it in the portal) and select an available number between c1 and c99. This means that there can only be 99 different auth context in your enterprise, but that number is unlikely to run out, because how many different ways can you ask a user to perform MFA?

The auth context object in itself requires a Conditional Access policy using it to be meaningful, because it is the CA policy that will enforce the MFA required. When you create a new CA policy, in the Target section, select Authentication context to what it applies to and check your auth context. In the Grant section, you can configure your requirements, like phishing resistant MFA and a compliant device.

WebApi code implementation
The endpoint for the sensitive operation in the webapi needs to check that the authentication context requirement is met and return a 401 Unauthorized with a challange if not. In code, this looks like below.
protected bool IsUserToken() {
if (HttpContext!.User == null) return false;
var scopeClaim = HttpContext.User.FindFirst(c => c.Type == ClaimConstants.Scope || c.Type == ClaimConstants.Scp);
if (scopeClaim == null) return false;
return true;
}
protected bool HasRequiredAuthContext() {
return User.FindFirst("acrs")?.Value == requiredAuthContextId;
}
[HttpGet("/api/sensitiveoperation")]
[Authorize(Policy = "ScopeOrRolePolicy.Read")]
[Produces("application/json")]
public async Task<ActionResult<string>> Get() {
if ( IsUserToken() && !HasRequiredAuthContext() ) {
return ReturnUnauthorizedAuthContext();
}
var resp = new { Message = "Sensitive operation completed successfully.", Timestamp = DateTime.UtcNow.ToString("o") };
string content = JsonSerializer.Serialize(resp, CompactJsonOptions);
return new ContentResult { ContentType = "application/json", Content = content, StatusCode = (int)HttpStatusCode.OK };
}
If your API endpoint accepts calls from both user and app tokens, you need to make sure you just enforce the auth context requirement when a user calls your API. Then if the selected value between c1 and c99 is not available in the acrs claim, the API needs to return a authentication challenge to the caller. The requiredAuthContextId would be a string with value like “c13”. The challange is returned in the HTTP response header with the well-known key of WWW-Authenticate. The value of that header key is base64 encoded JSON string saying that “the access_token must have the acr(s) claim with the value of c13 – and that is essential”.
protected UnauthorizedObjectResult ReturnUnauthorizedAuthContext() {
var claimsChallenge = new {
access_token = new {
acrs = new {
essential = true,
value = requiredAuthContextId
}
}
};
string base64Challenge = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(claimsChallenge)));
Response.Headers.Append("WWW-Authenticate", $"Bearer error=\"insufficient_claims\", claims=\"{base64Challenge}\"");
return Unauthorized("Step-up authentication is required for this action.");
}
WebApp code implementation
The webapp needs to be able to look at the WWW-Authenticate header when it gets a 401 Unauthorized response, otherwise there is no joy. In the MicrosoftIdentityWeb world, it does that for us and throws a specific exception that we can act upon. The exception has the very long name of MicrosoftIdentityWebChallageUserException, but the name is very correct – it is a user challange exception. The exception message will contain AADSTS50076 as the error code indicating that “you must use multi-factor authentication to access <appId>”. In the aspnet Razor Pages world, all you have to do then is to call ChallangeAsync with the claims passed in the exception.
try {
ApiResponseDTO resp = await _downstreamApi!.GetForUserAsync<ApiResponseDTO>("DownstreamAPI",
options => { options.RelativePath = "api/sensitiveoperation"; });
} catch (MicrosoftIdentityWebChallengeUserException miwcue) {
await HandleUserChallangeException(miwcue);
} catch (Exception ex) {
_log.LogError($"Exception: {ex.Message}");
}
private async Task HandleUserChallangeException(MicrosoftIdentityWebChallengeUserException miwcue ) {
var properties = new AuthenticationProperties();
if ( null != miwcue.MsalUiRequiredException
&& miwcue.MsalUiRequiredException.Message.Contains("AADSTS50076:")
&& null != miwcue.MsalUiRequiredException.Claims ) {
properties.Items.Add("claims", miwcue.MsalUiRequiredException.Claims);
}
await HttpContext.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, properties);
}
The claims in the exception will be something like, where the guid is the id of your CA policy that targets auth context ‘C13’. Passing this back to Entra tells it to execute the CA policy that will acquire the acr claim for you.
{"access_token":{"capolids":{"essential":true,"values":["f9028060-9ed6-4a55-a9d5-025e7b9aafa7"]}}}
Network trace – what is really happening on the wire?
- The wepapp calls the webapi with the access token without the required auth context
GET https://localhost:5011/api/sensitiveoperation
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Date: Fri, 02 Oct 2026 09:09:18 GMT
Server: Kestrel
Transfer-Encoding: chunked
WWW-Authenticate: Bearer error="insufficient_claims", claims="eyJhY2Nlc3NfdG9rZW4iOnsiYWNycyI6eyJlc3NlbnRpYWwiOnRydWUsInZhbHVlIjoiYzEzIn19fQ=="
The claims property is as explain above a base 64 encoded string that contains {“access_token”:{“acrs”:{“essential”:true,”value”:”c13″}}}, indicating that “you are missing c13 in your acr claim.
2. The webapp calls Entra to step in and acquire the required claim. It passes the decoded base64 string and the refresh token in order to acquire a new access token.
POST https://login.microsoftonline.com/9885457a-2026-4e2c-a47e-32ff52ea0b8d/oauth2/v2.0/token
...&claims=%7B%22access_token%22%3A%7B%22acrs%22%3A%7B%22essential%22%3Atrue%2C%22value%22%3A%22c13%22%7D%7D%7D&grant_type=refresh_token&refresh_token=...
Entra sees the request and responds with a redirect to the UI that performs the MFA. Once that is completed, and the upgraded access token is acquired, the aspnet middleware repeats the API call to the downstream webapi.
This is cool and I want to use it
Yes, it works really well and can help you with step-up MFA just-in-time. Just be aware of:
- Your webapp and webapi both needs to be developed for using Authentication Context. You can’t add it as an afterthought.
- If you develop a SaaS app, using ‘c13’ for your auth context shouldn’t be hard coded. The individual enterprise using your app should be able to choose between c1 and c99.
- Given the statement above, both the webapp and the webapi needs to agree that it is ‘c13’ they are using. If webapi is asking for ‘c87’ while webapp is using ‘c91’, it’s not going to work.
- If you have a webapi that is asking for ‘c13’ and you can’t modify your webapp (maybe because it’s a SaaS app), you can use a CA policy and enforce sign-in to the app to acquire ‘c13’ at login time and keep the webapi happy. It won’t be step-up, but atleast you can make it work (but remember that your dance card will not be as full at the X-mas party).