-
Notifications
You must be signed in to change notification settings - Fork 219
Managing incremental consent and conditional access
The Microsoft identity platform allows users to incrementally consent to your application access more resources / web APIs on their behalf (that is to consent to more scopes) as they are needed. This is called incremental consent. This means that, in a web app a controller action could require some scopes, and then another controller action could require more scopes, and this will trigger an interaction with the user of the web application so that the user can consent for these new scopes.
If you're building an Azure AD B2C application and use several user flows, you'll also need to handle incremental consent, as interaction will be required with the user.
You can also decide to not handle incremental consent. In that case you define the permissions at app registration, and have the tenant administrator consent to them all (admin consent). Then, as you request tokens, you'll can use the {resource}/.default syntax
to get an access token for all pre-approved scopes for this given resource. You can also request specific pre-approved scopes if you wish.
If, at some point, your app requests more scopes than what the admin has consented, you'll receive an MsalUiRequiredException
, and you'll know that you need to have more scopes pre-approved by the tenant admin.
If you are a Microsoft employee building an first party application, static permissions are the way to go.
It can also happen that, when requesting a token to call a (downstream) web API, your web app or web API receives a claims challenge exception instructing the app that the user needs to provide more claims (for instance the user needs to perform multi-factor authentication). This happens for some specific web APIs for which the tenant administrator had added conditional access policies. From your point of view, as an application developer, this looks the same as handing incremental consent: the user needs will get through a consent screen, which will trigger more user flows, such as performing multi-factor authentication.
You can choose to not handle incremental consent, however, you should handle conditional access, as your web app / API could be non-functional if installed in tenants where the tenant admins decide to enable conditional access. And given incremental consent and conditional access is handled similarly we recommend you handle these scenarios in your applications.
The way for your app to handle conditional access and incremental consent is different depending on if you are building:
- a web app (where interaction is possible with the user),
- or a web API (where interaction is not possible, and therefore where the information needs to be propagated back to the client).
For web apps, it's also different depending on the technology you use:
- ASP.NET Core MVC controller,
- Razor page
- or a Blazor page.
In MVC controllers, you'll need to use the [AuthorizeForScopes] attribute on each controller action.
[Authorize]
public class HomeController : Controller
{
private readonly ITokenAcquisition _tokenAcquisition;
public HomeController(ITokenAcquisition tokenAcquisition)
{
_tokenAcquisition = tokenAcquisition;
}
[AuthorizeForScopes(Scopes = new string[] {"user.read"})]
public async Task<IActionResult> Index()
{
var accessToken = tokenAcquisition.GetAccessTokenForUserAsync(new string[] {"user.read"});
// Call API
return View();
}
}
Alternatively, you can specify the scopes in the appsetttings.json file as a space separated strings
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
/* more here*/
},
"CalledApi": {
"CalledApiScopes": "user.read mail.read",
"CalledApiUrl": "https://graph.microsoft.com/v1.0"
},
In
[AuthorizeForScopes(ScopeKeySection = "CalledApi:CalledApiScopes")]
public async Task<IActionResult> Index()
{
var accessToken = tokenAcquisition.GetAccessTokenForUserAsync(scopesFromResources);
// Call API
return View();
}
In Razor pages, you'll need to use the [AuthorizeForScopes] attribute on the class representing the Razor page.
namespace RazorSample.Pages
{
[AuthorizeForScopes(ScopeKeySection = "CalledApi:CalledApiScopes")]
public class IndexModel : PageModel
{
private readonly GraphServiceClient _graphServiceClient;
public IndexModel(GraphServiceClient graphServiceClient)
{
_graphServiceClient = graphServiceClient;
}
public async Task OnGet()
{
var user = await _graphServiceClient.Me.Request().GetAsync();
ViewData["ApiResult"] = user.DisplayName;
}
}
In Blazor server, you'll need to inject a service, and catch the exceptions so that the user is re-signed-in and consents / performs conditional access. The code below presents a Blazor page named callwebapi in a Blazor server assembly.
You'll need to register the Microsoft Identity consent and conditional access handler service. For this, in startup.cs
Replace
services.AddServerSideBlazor();
by
services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
In each Blazor page acquiring tokens, you'll use the Microsoft Identity consent and conditional access handler service to handle the exception:
You need to:
-
add a using for Microsoft.Identity.Web
-
inject the
MicrosoftIdentityConsentAndConditionalAccessHandler
service.@using Microsoft.Identity.Web @inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
-
When you acquire your token (or call a method that acquires a token), if you get an exception, you'll need to process it with the
MicrosoftIdentityConsentAndConditionalAccessHandler
:catch (Exception ex) { ConsentHandler.HandleException(ex); }
For instance:
@page "/callwebapi"
@using MySample
@using Microsoft.Identity.Web
@inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
@inject IDownstreamWebApi downstreamAPI
<h1>Call an API</h1>
<p>This component demonstrates fetching data from a Web API.</p>
@if (apiResult == null)
{
<p><em>Loading...</em></p>
}
else
{
<h2>API Result</h2>
@apiResult
}
@code {
private string apiResult;
protected override async Task OnInitializedAsync()
{
try
{
// downstreamAPI.CallWebApiAsync calls ITokenAcquisition.GetAccessTokenForUserAsync
apiResult = await downstreamAPI.CallWebApiAsync("me");
}
catch (Exception ex)
{
ConsentHandler.HandleException(ex);
}
}
}
In a Web API, your controller action will need to explicity call ITokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeaderAsync
so that the Web API replies to the client with a wwwAuthenticate header containing information about missing claims.
public async Task<string> CallGraphApiOnBehalfOfUser()
{
string[] scopes = { "user.read" };
// we use MSAL.NET to get a token to call the API On Behalf Of the current user
try
{
string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes);
dynamic me = await CallGraphApiOnBehalfOfUser(accessToken);
return me.UserPrincipalName;
}
catch (MsalUiRequiredException ex)
{
await _tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeaderAsync(scopes, ex);
return string.Empty;
}
}
- Home
- Why use Microsoft Identity Web?
- Web apps
- Web APIs
- Using certificates
- Minimal support for .NET FW Classic
- Logging
- Azure AD B2C limitations
- Samples
- Web apps
- Web app samples
- Web app template
- Call an API from a web app
- Managing incremental consent and conditional access
- Web app troubleshooting
- Deploy to App Services Linux containers or with proxies
- SameSite cookies
- Hybrid SPA
- Web APIs
- Web API samples
- Web API template
- Call an API from a web API
- Token Decryption
- Web API troubleshooting
- web API protected by ACLs instead of app roles
- gRPC apps
- Azure Functions
- Long running processes in web APIs
- Authorization policies
- Generic API
- Customization
- Logging
- Calling graph with specific scopes/tenant
- Multiple Authentication Schemes
- Utility classes
- Setting FIC+MSI
- Mixing web app and web API
- Deploying to Azure App Services
- Azure AD B2C issuer claim support
- Performance
- specify Microsoft Graph scopes and app-permissions
- Integrate with Azure App Services authentication
- Ajax calls and incremental consent and conditional access
- Back channel proxys
- Client capabilities