-
Notifications
You must be signed in to change notification settings - Fork 217
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.json
file. -
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.
- 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