# How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications This guide demonstrates how to integrate AzureAD to an ABP application that enables users to sign in using OAuth 2.0 with credentials from **Azure Active Directory**. Adding Azure Active Directory is pretty straightforward in ABP framework. Couple of configurations needs to be done correctly. Two different **alternative approaches** for AzureAD integration will be demonstrated for better coverage. 1. **AddAzureAD**: This approach uses Microsoft [AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/) which is very popular when users search the web about how to integrate AzureAD to their web application. 2. **AddOpenIdConnect**: This approach uses default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for not only AzureAD but for all OpenId connections. > There is **no difference** in functionality between these approaches. AddAzureAD is an abstracted way of OpenIdConnection ([source](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADAuthenticationBuilderExtensions.cs#L122)) with predefined cookie settings. > > However there are key differences in integration to ABP applications because of default configurated signin schemes which will be explained below. ## 1. AddAzureAD This approach uses the most common way to integrate AzureAD by using the [Microsoft AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/). If you choose this approach, you will need to install `Microsoft.AspNetCore.Authentication.AzureAD.UI` package to your **.Web** project. Also, since AddAzureAD extension uses [configuration binding](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#default-configuration), you need to update your appsettings.json file located in your **.Web** project. #### **Updating `appsettings.json`** You need to add a new section to your `appsettings.json` which will be binded to configuration when configuring the `OpenIdConnectOptions`: ````json "AzureAd": { "Instance": "https://login.microsoftonline.com/", "TenantId": "", "ClientId": "", "Domain": "domain.onmicrosoft.com", "CallbackPath": "/signin-azuread-oidc" } ```` > Important configuration here is the CallbackPath. This value must be the same with one of your Azure AD-> app registrations-> Authentication -> RedirectUri. Then, you need to configure the `OpenIdConnectOptions` to complete the integration. #### Configuring OpenIdConnectOptions In your **.Web** project, locate your **ApplicationWebModule** and modify `ConfigureAuthentication` method with the following: ````csharp private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); context.Services.AddAuthentication() .AddIdentityServerAuthentication(options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = false; options.ApiName = "Acme.BookStore"; }) .AddAzureAD(options => configuration.Bind("AzureAd", options)); context.Services.Configure(AzureADDefaults.OpenIdScheme, options => { options.Authority = options.Authority + "/v2.0/"; options.ClientId = configuration["AzureAd:ClientId"]; options.CallbackPath = configuration["AzureAd:CallbackPath"]; options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.RequireHttpsMetadata = false; options.TokenValidationParameters.ValidateIssuer = false; options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.SignInScheme = IdentityConstants.ExternalScheme; options.Scope.Add("email"); }); } ```` > **Don't forget to:** > > * Add `.AddAzureAD(options => configuration.Bind("AzureAd", options))` after `.AddAuthentication()`. This binds your AzureAD appsettings and easy to miss out. > * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`. This will disable the default Microsoft claim type mapping. > * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier)`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information. > * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35). > * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](../Modules/Account.md) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215). You are done and integration is completed. ## 2. Alternative Approach: AddOpenIdConnect If you don't want to use an extra nuget package in your application, you can use the straight default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for all OpenId connections including AzureAD external authentication. You don't have to use `appsettings.json` configuration but it is a good practice to set AzureAD information in the `appsettings.json`. To get the AzureAD information from `appsettings.json`, which will be used in `OpenIdConnectOptions` configuration, simply add a new section to `appsettings.json` located in your **.Web** project: ````json "AzureAd": { "Instance": "https://login.microsoftonline.com/", "TenantId": "", "ClientId": "", "Domain": "domain.onmicrosoft.com", "CallbackPath": "/signin-azuread-oidc" } ```` Then, In your **.Web** project; you can modify the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following: ````csharp private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); context.Services.AddAuthentication() .AddIdentityServerAuthentication(options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = false; options.ApiName = "BookStore"; }) .AddOpenIdConnect("AzureOpenId", "Azure Active Directory OpenId", options => { options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/"; options.ClientId = configuration["AzureAd:ClientId"]; options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.CallbackPath = configuration["AzureAd:CallbackPath"]; options.RequireHttpsMetadata = false; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("email"); }); } ```` And that's it, integration is completed. Keep on mind that you can connect any other external authentication providers. ## The Source Code You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization). # FAQ * Help! `GetExternalLoginInfoAsync` returns `null`! * There can be 2 reasons for this; 1. You are trying to authenticate against wrong scheme. Check if you set **SignInScheme** to `IdentityConstants.ExternalScheme`: ````csharp options.SignInScheme = IdentityConstants.ExternalScheme; ```` 2. Your `ClaimTypes.NameIdentifier` is `null`. Check if you added claim mapping: ````csharp JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); ```` * Help! I keep getting ***AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application*** error! * If you set your **CallbackPath** in appsettings as: ````csharp "AzureAd": { ... "CallbackPath": "/signin-azuread-oidc" } ```` your **Redirect URI** of your application in azure portal must be with domain like `https://localhost:44320/signin-azuread-oidc`, not only `/signin-azuread-oidc`. * Help! I keep getting ***AADSTS700051: The response_type 'token' is not enabled for the application.*** error! * This error occurs when you request **token** (access token) along with **id_token** without enabling Access tokens on Azure portal app registrations. Simply tick **Access tokens** checkbox located on top of ID tokens to be able to request token aswell. * Help! I am getting ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** error! * This occurs when you use Azure Authority **v2.0 endpoint** without requesting `email` scope. [Abp checks unique email to create user](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L208). Simply add ````csharp options.Scope.Add("email"); ```` to your openid configuration. * Help! I keep getting ***AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret*** error! * This error occurs when you request **code** along with **id_token**. You need to add **client secret** on azure portal app registrations, under **Certificates & secrets** menu. Afterwards, you need to add openid configuration option like: ````csharp options.ClientSecret = "Value of your secret on azure portal"; ```` * How can I **debug/watch** which claims I get before they get mapped? * You can add a simple event under openid configuration to debug before mapping like: ````csharp options.Events.OnTokenValidated = (async context => { var claimsFromOidcProvider = context.Principal.Claims.ToList(); await Task.CompletedTask; }); ```` ## See Also * [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md). * [How to Customize the SignIn Manager for ABP Applications](Customize-SignIn-Manager.md).