-
Notifications
You must be signed in to change notification settings - Fork 242
Web Apps
Microsoft identity web supports ASP.NET Core web apps that sign-in users in Microsoft Entra ID, Azure AD B2C, and Microsoft Entra External IDs. Optionally these apps can call downstream web APIs. Web apps typically run on a server and serve HTML pages.
See:
- Create a new ASP.NET Core web app using the command line, or the Visual Studio wizard.
- Add authentication to an existing web app
- Migrate an ASP.NET Core 3.1 web app to use Microsoft Identity Web
You can enable your web app to allow users to sign up and create a new guest account. First, set up your tenant and the app as described in Add a self-service sign-up user flow to an app. Next, set Prompt property in OpenIdConnectOptions (or in MicrosoftIdentityOptions which inherits from it) to "create" to trigger the sign-up experience. Your app can have a Sign up button linking to an Action which sets the Prompt property, like in the example below. After the user goes through the sign-up process, they will be logged into the app.
[HttpGet("{scheme?}")]
public IActionResult SignUp([FromRoute] string scheme)
{
scheme ??= OpenIdConnectDefaults.AuthenticationScheme;
var parameters = new Dictionary<string, object>
{
{ "prompt", "create" },
};
OAuthChallengeProperties oAuthChallengeProperties = new OAuthChallengeProperties(new Dictionary<string, string>(), parameters);
oAuthChallengeProperties.RedirectUri = Url.Content("~/");
return Challenge(
oAuthChallengeProperties,
scheme);
}AddMicrosoftIdentityWebApp (applied to authentication builders) has another override, which takes delegates instead of a configuration section. The override with
a configuration section actually calls the override with delegates. See the source code for AddMicrosoftIdentityWebApp with configuration section
In advanced scenarios you might want to add configuration by code, or if you want to subscribe to OpenIdConnect events. For instance if you want to provide a custom processing when the token is validated, you could use code like the following:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
Configuration.Bind("AzureAD", options);
options.Events ??= new OpenIdConnectEvents();
options.Events.OnTokenValidated += OnTokenValidatedFunc;
});with OnTokenValidatedFunc like the following:
private async Task OnTokenValidatedFunc(TokenValidatedContext context)
{
// Custom code here
await Task.CompletedTask.ConfigureAwait(false);
}In the above code, your handler will be executed after any existing handlers. In the below code, your code will be executed before any other existing handlers.
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
Configuration.Bind("AzureAD", options);
options.Events ??= new OpenIdConnectEvents();
var existingHandlers = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = OnTokenValidatedFunc;
options.Events.OnTokenValidated += existingHandlers;
});This override also allows you to set an optional delegate to initialize the CookiesAuthenticationOptions.
In more advanced scenarios, for instance if you use several IdPs, you might want to specify an authentication scheme (here using the default authentication scheme, which makes this code snippet equivalent to the previous one)
services.AddAuthentication("MyAuthenticationScheme")
.AddMicrosoftIdentityWebApp(Configuration,
openIdConnectAuthenticationScheme: "MyAuthenticationScheme");See also:
- ASP.NET Core Web app incremental tutorial chapter 1.1, Sign in users in your organization
- Web App that signs-in users scenario overview in the Microsoft identity platform documentation, and related articles
This example, demonstrates how to programmatically trigger a B2C user flow from a controller action or razor. In the AuthorizeForScopes attribute, you will define the scopes and B2C user flow, and then include the same in GetAccessTokenForUserAsync. Microsoft Identity Web will handle the challenge for you.
[AuthorizeForScopes(Scopes = new string[] { Scope }, UserFlow = EditProfile)] // Must be the same user flow as used in `GetAccessTokenForUserAsync()`
public async Task<ActionResult> ClaimsEditProfile()
{
// We get a token, but we don't use it. It's only to trigger the user flow
await _tokenAcquisition.GetAccessTokenForUserAsync(
new string[] { Scope },
userFlow: EditProfile);
return View(Claims, null);
}If you want your web app to call web APIs, add the .EnableTokenAcquisitionToCallDownstreamApi() line, and then choose a token cache implementation, for example .AddInMemoryTokenCaches():


using Microsoft.Identity.Web;
public class Startup
{
const string scopesToRequest = "user.read";
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { scopesToRequest })
.AddInMemoryTokenCaches();
...
}
...
}By default, AddMicrosoftIdentityWebAppAuthentication and the override of AddMicrosoftIdentityWebApp taking a configuration object get the configuration from the "AzureAD" section of the configuration files. It has
several parameters you can change.
The proposed token cache serialization is in memory. You can also use the session cache, or various distributed caches.
Note that you don't need to pass-in the scopes to request when calling EnableTokenAcquisitionToCallDownstreamApi. You can do that just in time in the controller (see Web app controller below)
using Microsoft.Identity.Web;
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
...
}
...
}For your web app to call web APIs on behalf of the signed-in user, add a parameter of type ITokenAcquisition to the constructor of your controller (the ITokenAcquisition service will be injected by dependency injection by ASP.NET Core).

using Microsoft.Identity.Web;
[Authorize]
public class HomeController : Controller
{
readonly ITokenAcquisition tokenAcquisition;
public HomeController(ITokenAcquisition tokenAcquisition)
{
this.tokenAcquisition = tokenAcquisition;
}
...Then, in your controller actions, call ITokenAcquisition.GetAccessTokenForUserAsync, passing the scopes for which to request a token. The other methods of ITokenAcquisition are used from the EnableTokenAcquisitionToCallDownstreamApi() method and similar methods for web APIs (see below).
[Authorize]
public class HomeController : Controller
{
readonly ITokenAcquisition tokenAcquisition;
...
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public async Task<IActionResult> Action()
{
string[] scopes = new []{"user.read"};
string token = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
...
// call the downstream API with the bearer token in the Authorize header
}The controller action is decorated with the AuthorizeForScopesAttribute which enables it to process the MsalUiRequiredException that could be thrown by the service implementing ITokenAcquisition.GetAccessTokenForUserAsync. The web app can then interact with the user and ask them to consent to the scopes, or re-sign in if needed.

If your application wants to call a web API on behalf of itself (not of behalf of a user), you can use ITokenAcquisition.GetAccessTokenForAppAsync in the controller. The code in the startup.cs file is the same as when you call an API on behalf of a user, and the constructor of your controller or Razor page injects an ITokenAcquisition service.
[Authorize]
public class HomeController : Controller
{
readonly ITokenAcquisition tokenAcquisition;
...
public async Task<IActionResult> Action()
{
string[] scopes = new []{"users.read.all"};
string token = await tokenAcquisition.GetAccessTokenForAppAsync(scopes);
...
// call the downstream API with the bearer token in the Authorize header
}For more details on the end to end scenario, see:
In web apps, Microsoft.Identity.Web leverages the following OAuth 2.0 protocols:
- OAuth 2.0 authorization code flow and Token refresh for GetTokenForUser
- OAuth 2.0 client credentials flow for GetTokenForApp.
If you have an AAD B2C web app, which just signs in users (does not call a web API), and you include a valid client secret for the web app in the appsettings.json file, you will get this error when switching between policies:
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler: Error: Message contains error: 'invalid_grant', error_description: 'AADB2C90088: The provided grant has not been issued for this endpoint. Actual Value : B2C_1_susi_v3 and Expected Value : B2C_1_edit_profile
Do one of the following:
-
Remove the client secret in
appsettings.json. If the web app is only signing in users and NOT calling a downstream API, it does not need to have the client secret included in theappsettings.jsonfile. -
Enable MSAL .NET to correctly handle the auth code redemntion by including the following in
Startup.cs:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration, "AzureAdB2C")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();This occurs because each policy, or user flow, is a separate authorization server in AAD B2C, meaning each user flow issues their own tokens.
When only signing-in users, your client app only needs the ID token, which contains information about the signed in user. However, if the client secret is included in the appsettings.json, Microsoft Identity Web assumes the web app will eventually call a downstream API, so it requests a code and ID token. ASP .NET then receives the code, but cannot complete the authorization code flow, so MSAL .NET is invoked to redeem the code for tokens, but the ID token and the code were not provided by the same authority (for example, one for su_si policy and one for edit_profile policy), so an invalid_grant error is thrown.
By default, when using AddMicrosoftIdentityWebAppAuthentication(), the application's cookie authentication session persists independently of your Azure AD B2C token lifetime settings.
This means:
- Your B2C user flow might be configured for a 1-hour token lifetime
- But the user's application session remains valid for ~14 days
- The token expiration configured in B2C is not automatically enforced by the application
###The Solution
To make your application respect the token lifetime configured in your B2C user flow, set UseTokenLifetime = true:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
configuration.Bind("AzureAdB2C", options);
options.UseTokenLifetime = true;
options.GetClaimsFromUserInfoEndpoint = true;
});What this does:
- Ties the application session lifetime to the token's expiration
- When the token expires, the user must re-authenticate
- Respects the Access & ID token lifetimes value set in your Azure AD B2C user flow
You can inspect the token's issue and expiry times:
AuthenticateResult authenticate = await HttpContext.AuthenticateAsync();
DateTimeOffset? issuedUtc = authenticate.Properties.IssuedUtc;
DateTimeOffset? expiresUtc = authenticate.Properties.ExpiresUtc;When to Use This
Use UseTokenLifetime = true when:
- You want tight control over session duration
- Your security policies require tokens to expire after a specific time
- You need the application session to align with B2C token lifetime policies
- Home
- Why use Microsoft Identity Web?
- Web apps
- Web APIs
- Minimal support for .NET FW Classic
- Logging
- Azure AD B2C limitations
- Samples
- Certificates
- Managed Identity as Federated Credential
- Federated Credentials from other Identity Provider
- Extensibility: Bring your own credential
- Get client secrets from KeyVault
- 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