How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications

This document has a newer version: https://community.abp.io/posts/how-to-use-the-azure-active-directory-authentication-for-mvc-razor-page-applications-4603b9cf

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 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 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) 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.

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, 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:

  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<your-tenant-id>",
    "ClientId": "<your-client-id>",
    "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:

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<OpenIdConnectOptions>(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 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.
  • 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 uses email claim to register external users.

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 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:

  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<your-tenant-id>",
    "ClientId": "<your-client-id>",
    "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:

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.

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:

        options.SignInScheme = IdentityConstants.ExternalScheme;
        
      2. Your ClaimTypes.NameIdentifier is null. Check if you added claim mapping:

        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:

        "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 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. Simply add

      options.Scope.Add("email");
      

      to your openid configuration.

  • 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:

      options.Events.OnTokenValidated = (async context =>
      {
      	var claimsFromOidcProvider = context.Principal.Claims.ToList();
      	await Task.CompletedTask;
      });
      

See Also

Contributors


Last updated: May 24, 2022 Edit this page on GitHub

Was this page helpful?

Please make a selection.

To help us improve, please share your reason for the negative feedback in the field below.

Please enter a note.

Thank you for your valuable feedback!

Please note that although we cannot respond to feedback, our team will use your comments to improve the experience.

In this document
Community Talks

What’s New with .NET 9 & ABP 9?

21 Nov, 17:00
Online
Watch the Event
Mastering ABP Framework Book
Mastering ABP Framework

This book will help you gain a complete understanding of the framework and modern web application development techniques.

Learn More