Azure AD B2C With ASP.NET Core 2.0

In this writeup, I’ll demonstrate how to use Azure AD B2C to delegate identity and access management to Azure. One of the key difference is that we will not pre-register users in Azure AD using Azure AD domain name, like previous post, instead consumers of our applications can create users using any domain e.g. gmail.com.

Problem

How to implement Azure AD B2C authentication in ASP.NET Core 2.0

Solution

In previous posts, we have seen how to secure our applications using:

  • Custom login/logout pages using Cookie Authentication.
  • JWT to secure web services.
  • NET Identity to simplify identity and access management.
  • IdentityServer4 to delegate identity and access management to a dedicated application.
  • Azure AD to delegate identity and access management to Azure

In this, I’ll demonstrate how to use Azure AD B2C to delegate identity and access management to Azure. One of the key differences is that we will not pre-register users in Azure AD using Azure AD domain name, like the previous post, instead consumers of our applications can create users using any domain e.g. gmail.com

Configure Azure AD B2C

Create Azure subscription (start for free, gives you credit to play).

Create new resource and search for ‘azure active directory b2c’,

ASP.NET Core

We’ll set up a new directory:

ASP.NET Core

Enter details, including the unique ‘initial domain name’, this will become the Tenant Name that will be used in your application e.g. below the tenant name will be fiveradb2c.onmicrosoft.com,

ASP.NET Core

Once the directory is created, we need to register our application,

ASP.NET Core

Once the application is created, you’ll see ‘Application ID’ in the list. This will be used in our application later as ‘Client ID’. Make a note of this.

Click on the application and ‘Properties’, enter Reply URL where Azure AD will redirect the user after sign-in, sign-out, sign-up and edit profile,

ASP.NET Core

Note that these URLs are used by the middleware and have to match the ones shown above.

Next we’ll setup policies for sign-in, sign-up and edit profile,

ASP.NET Core

Here I’ll show one policy (sign-up or sign-in) as all of them are very similar. First we’ll setup identity provider,

ASP.NET Core

The next properties/attributes that user will fill during sign-up,

ASP.NET Core

The next claims that will be returned to your application,

ASP.NET Core

Once you’ve setup all the policies you’ll see them under ‘All Policies’. Note that Azure has prepended them with ‘B2C_1_’,

ASP.NET Core

So far you have,

  1. Created Azure AD B2C
  2. Registered your application and reply URLs
  3. Registered policies for sign-in, sign-up and profile editing.

Next we’ll setup our application and use Azure AD B2C to register and authenticate users.

Configure Application

Create an empty project and update Startup to configure services and middleware for MVC and Authentication

  1. private readonly string TenantName = ""// aka 'Initial Domain Name' in Azure  
  2.      private readonly string ClientId = ""// aka 'Application Id' in Azure  
  3.   
  4.      public void ConfigureServices(IServiceCollection services)  
  5.      {  
  6.          var signUpPolicy = "B2C_1_sign_up";  
  7.          var signInPolicy = "B2C_1_sign_in";  
  8.          var signUpInPolicy = "B2C_1_sign_up_in";  
  9.          var editProfilePolicy = "B2C_1_edit_profile";  
  10.   
  11.          services.AddAuthentication(options =>  
  12.          {  
  13.              options.DefaultAuthenticateScheme =   
  14.                 CookieAuthenticationDefaults.AuthenticationScheme;  
  15.              options.DefaultSignInScheme =   
  16.                 CookieAuthenticationDefaults.AuthenticationScheme;  
  17.              options.DefaultChallengeScheme = signUpInPolicy;  
  18.          })  
  19.          .AddOpenIdConnect(signUpPolicy, GetOpenIdConnectOptions(signUpPolicy))  
  20.          .AddOpenIdConnect(signInPolicy, GetOpenIdConnectOptions(signInPolicy))  
  21.          .AddOpenIdConnect(signUpInPolicy, GetOpenIdConnectOptions(signUpInPolicy))  
  22.          .AddOpenIdConnect(editProfilePolicy,   
  23.                                GetOpenIdConnectOptions(editProfilePolicy))  
  24.          .AddCookie();  
  25.   
  26.          services.AddMvc();  
  27.      }  
  28.   
  29.      public void Configure(IApplicationBuilder app, IHostingEnvironment env)  
  30.      {  
  31.          app.UseAuthentication();  
  32.          app.UseMvcWithDefaultRoute();  
  33.      } 

Note that we have to add middleware to handle each policy setup on Azure AD B2C, to avoid repetition I’ve added GetOpenIdConnectOptions method:

  1. private Action<OpenIdConnectOptions> GetOpenIdConnectOptions(string policy)  
  2.         => options =>  
  3.         {  
  4.             options.MetadataAddress =   
  5.                "https://login.microsoftonline.com/" + this.TenantName +   
  6.                "/v2.0/.well-known/openid-configuration?p=" + policy;  
  7.             options.ClientId = this.ClientId;  
  8.             options.ResponseType = OpenIdConnectResponseType.IdToken;  
  9.             options.CallbackPath = "/signin/" + policy;  
  10.             options.SignedOutCallbackPath = "/signout/" + policy;  
  11.             options.SignedOutRedirectUri = "/";  
  12.         };  

Here we are setting up Open ID Connect authentication middleware with,

  • MetadataAddress
    Path to the discovery endpoint to get metadata about our policy.

  • ClientId
    Application identifier that Azure AD B2C provides for our application.

  • ResponseType
    Value that determines authorization flow used and parameters returned from server. We are interested only in authorization token for now.

  • CallbackPath
    Path where server will redirect after authentication. We don’t need to create this in our application, the middleware will handle this.

  • SignedOutCallbackPath
    Path where server will redirect after signing out.

  • SignedOutRedirectUri
    Path where application will redirect after signing out. This is the path to our application home page, for instance.

Also when configuring authentication middleware, we’ve specified sign-up or sign-in as challenge scheme. This is the middleware ASP.NET Core will use for users who have not been signed in. Azure AD B2C will present them with login page with option to sign-up too.

Once signed in, we want to store their identity in a cookie, instead of redirecting to authentication server for every request. For this reason we’ve added cookie authentication middleware and are using that as default sign-in scheme. If the cookie is missing, users will be ‘challenged’ for their identity.

Add a controller to implement login, logout, sign-up and profile editing actions:

  1. public class SecurityController : Controller  
  2.   {  
  3.       public IActionResult Login()  
  4.       {  
  5.           return Challenge(  
  6.              new AuthenticationProperties { RedirectUri = "/" }, "B2C_1_sign_in");  
  7.       }  
  8.   
  9.       [HttpPost]  
  10.       public async Task Logout()  
  11.       {  
  12.           await HttpContext.SignOutAsync(  
  13.               CookieAuthenticationDefaults.AuthenticationScheme);  
  14.   
  15.           var scheme = User.FindFirst("tfp").Value;  
  16.           await HttpContext.SignOutAsync(scheme);  
  17.       }  
  18.   
  19.       [Route("signup")]  
  20.       public async Task<IActionResult> SignUp()  
  21.       {  
  22.           return Challenge(  
  23.               new AuthenticationProperties { RedirectUri = "/" }, "B2C_1_sign_up");  
  24.       }  
  25.   
  26.       [Route("editprofile")]  
  27.       public IActionResult EditProfile()  
  28.       {  
  29.           return Challenge(  
  30.              new AuthenticationProperties { RedirectUri = "/" },   
  31.              "B2C_1_edit_profile");  
  32.       }  
  33.   }  

When logging out it is important to sign-out from authentication server (Azure AD B2C) and also remove the cookie by signing out from cookie authentication scheme. Azure AD B2C sends the current profile name as tfp claim type, we use that to sign-out from the policy currently in use.

Run the application,

ASP.NET Core

Sign-up a new user,

ASP.NET Core

Then login,

ASP.NET Core

You’ll be redirected to your application,

ASP.NET Core

You could edit the profile too,

ASP.NET Core

When cancelling profile edit (or sign-up), Azure AD redirects back to your application but with an error, in order to catch that error and redirect to your home page we can handle event when setting up Open ID Connect middleware:

  1. private Action<OpenIdConnectOptions> GetOpenIdConnectOptions(string policy)  
  2.         => options =>  
  3.         {  
  4.             ...  
  5.   
  6.             options.Events.OnMessageReceived = context =>  
  7.             {  
  8.                 if (!string.IsNullOrEmpty(context.ProtocolMessage.Error) &&  
  9.                       !string.IsNullOrEmpty(context.ProtocolMessage.ErrorDescription) &&                     
  10.                        context.ProtocolMessage.ErrorDescription.StartsWith("AADB2C90091"))  
  11.                 {  
  12.                     context.Response.Redirect("/");  
  13.                     context.HandleResponse();  
  14.                 }  
  15.   
  16.                 return Task.FromResult(0);  
  17.             };  
  18.         }; 

Source Code