In this article, I will explain how you can authenticate your ASP.NET MVC application against Azure B2C Active Directory.

Complete source code for the application is available on my GitHub repo : https://github.com/sudipta-chaudhari/Azure_AD_B2C_MVC_Authentication

You need to change the web.config Azure AD B2C setings as mentioned at the end of Setp 4.

Step 1 : Create ASP.NET MVC Project

I have used Visual Studio 2017 Enterprise edition. Open Visual Studio, go to File -> New -> Project -> ASP.NET Application (.NET Framework) -> Select ‘MVC’ template.

Azure_AD_B2C_MVC_Step1

Enable SSL for the project by selecting the project in Visual Studio and clicking ‘F4’. This will bring up the Project Properties. Set “SSL Enabled” to “True”, copy the “SSL URL” as shown in the below screenshot.

Azure_AD_B2C_MVC_Step1_1

Right click on project, select “Properties” and paste the copied SSL URL in the “Project Url” textbox as shown in the screenshot below and save the changes by clicking ‘Save’ button.

Azure_AD_B2C_MVC_Step1_2

Step 2 : Install/Update NuGet Packages

Install below mentioned NuGet packages.

  • Microsoft.Owin.Security.OpenIdConnect
  • Microsoft.Owin.Security.Cookies
  • Microsoft.Owin.Host.SystemWeb

Update the below mentioned NuGet package.

  • Microsoft.IdentityModel.Protocol.Extensions.dll

This is needed else a HTTP 404 error is thrown when trying to login.

Microsoft.Owin.Security.OpenIdConnect is middleware to protect web apps with OpenId Connect.

OpenID Connect is a simple identity layer on top of the OAuth 2.0 protocol, which allows computing clients to verify the identity of an end-user based on the authentication performed by an authorization server, as well as to obtain basic profile information about the end-user in an interoperable and REST-like manner.

Microsoft.IdentityModel.Protocol.Extension contains classes which extends .NET Framework with base constructs from the OpenId Connect and WS-Federation protocols.

Microsoft.Owin.Security.Cookiesmiddleware enables an application to use cookie based authentication, similar to ASP.NET’s forms authentication. This middleware will be used to create a cookie based session after obtaining a valid token from our Azure AD B2C tenant. This cookie will be sent from the browser to the server with each subsequent request and get validated by cookie middleware.

Step 3 : Create application in Azure B2C Active Directory

Open the old Azure Portal https://manage.windowsazure.com Go to ‘Active Directory’ section. This will list all the active directories. Click on the name of your Azure AD which you intend to use and then from the AD home screen, click on “Manage B2C settings”.

Azure_AD_B2C_MVC_Step3_1

This will redirect you to the new Azure portal https://portal.azure.com and take you to the “Azure AD B2C Settings” screen as shown below.

AzureAD_B2C_MVC_Step3_2

On this screen, click the “Applications” section and click the Add button to register your ASP.NET MVC web app.

AzureAD_B2C_MVC_Step3_3

Under ‘Name’ textbox, enter the name for your application. Set ‘Web App/Web API’ to true, ‘Allow implicit flow’ to true and in the ‘Reply URL’ textbox, enter the SSL URL obtained from Step 1 and click ‘Save’. This will generate the ‘Application ID’.

Now the application configuration in Azure B2C AD is complete.

Step 4 : Add Owin Startup class

Add a OWIN Startup class to the project by right clicking the project, select ‘Add New Item’ and select ‘OWIN Startup class’. The code for OWIN Startup class is as shown below:-

public class Startup
{
    private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
    private static string aadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
    private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
    private static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
    public static string SignInPolicyId = ConfigurationManager.AppSettings["ida:SignInPolicyId"];
    public static string ProfilePolicyId = ConfigurationManager.AppSettings["ida:UserProfilePolicyId"];
    public void Configuration(IAppBuilder app)
    {
        ConfigureAuth(app);
    }
    public void ConfigureAuth(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());
        app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignInPolicyId));
    }
    private Task AuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
    {
        notification.HandleResponse();
        if (notification.Exception.Message == "access_denied")
        {
            notification.Response.Redirect("/");
        }
        else
        {
            notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message);
        }

        return Task.FromResult(0);
    }
    private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
    {
        return new OpenIdConnectAuthenticationOptions
        {
            MetadataAddress = String.Format(aadInstance, tenant, policy),
            AuthenticationType = policy,
            ClientId = clientId,
            RedirectUri = redirectUri,
            PostLogoutRedirectUri = redirectUri,
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthenticationFailed = AuthenticationFailed
            },
            Scope = "openid",
            ResponseType = "id_token",
            TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = "name",
                SaveSigninToken = true
            }
        };
    }
}

This class configures the OWIN OpenId Connect middleware. Configuration method accepts parameter of type IAppBuilder which will be supplied by the host at run-time.app parameter is an interface of type IAppBuilder which will be used to compose the application for our Owin server.

The application keys read in the above class which need to be added to web.config file are as below:-

<add key="ida:Tenant" value="b2ctest.onmicrosoft.com" />
<add key="ida:ClientId" value="12f991d7-4e82-4057-ae64-02c949a78ef3" />
<add key="ida:AadInstance" value="https://login.microsoftonline.com/{0}/v2.0/.well-known/openid-configuration?p={1}" />
<add key="ida:SignInPolicyId" value="B2C_1_MyOrg_Global_SignIn" />
<add key="ida:RedirectUri" value="https://localhost:44365/" />

Step 5 : MVC View/Controller with SignIn and SignOut button

Create a new MVC Controller named AccountController and add the below code for the SignIn and SignOut buttons.

public class AccountController : Controller
{
    public void SignIn()
    {
        if (!Request.IsAuthenticated)
        {
            HttpContext.GetOwinContext().Authentication.Challenge(
                new AuthenticationProperties() { RedirectUri = "/" }, Startup.SignInPolicyId);
        }
    }
    public void SignOut()
    {
        if (Request.IsAuthenticated)
        {
            IEnumerable<AuthenticationDescription> authTypes = HttpContext.GetOwinContext().Authentication.GetAuthenticationTypes();
            HttpContext.GetOwinContext().Authentication.SignOut(authTypes.Select(t => t.AuthenticationType).ToArray());
        }
    }
}

Add a partial view named _LoginPartial.cshtml which will call the SignIn and SignOut controller actions. The code for the partial view is as below.

@if (Request.IsAuthenticated)
{
    <text>
        <ul class="nav navbar-nav navbar-right">
            <li>
                <a id="profile-link">@User.Identity.Name</a>
            </li>
            <li>
                @Html.ActionLink("Sign out", "SignOut", "Account")
            </li>
        </ul>
    </text>
}
else
{
    <ul class="nav navbar-nav navbar-right">
        <li>@Html.ActionLink("Sign in", "SignIn", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
    </ul>
}

We now reference this partial view in the “_Layout.cshtml”. in the section, remove the existing content and paste the code as shown below.

<div class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li>@Html.ActionLink("Home", "Index", "Home")</li>
                <li>@Html.ActionLink("About", "About", "Home")</li>
                <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
            </ul>
            @Html.Partial("_LoginPartial")
        </div>
    </div>
</div>
 
<div class="container body-content">
    @RenderBody()
    <hr />
    <footer>
        <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
    </footer>
</div>

Now,  we are ready to run the application. On running the application, the application home page is as shown in the below screenshot where the ‘Sign In’ button can be seen.

AzureAD_B2C_MVC_HomePage

On clicking the ‘Sign In’ button, The application takes us to the Azure B2C AD Sign In page as shown in the below screenshot.

AzureAD_B2C_MVC_Loginpage

As can be seen in the above screenshot, I am signing into the Azure B2C AD with my Gmail Id and not my organizational mail id. This is the feature of Business to Consumer Azure Active Directory which supports external users other than the organizational users.

After providing my credentials, I am redirected back to the application’s home page (which is the same as configured in the ‘Reply Url’ in Step 3 while registering the application in the Azure B2C AD).

AzureAD_B2C_MVC_LoggedInPage

In the above screenshot, you can see my profile name preceding the ‘Sign out’ link.

The following diagram shows what happens when the user signs in, at a high level.

auth-flow

  1. The user clicks the “sign in” button in the app. This action is handled by an MVC controller.
  2. The MVC controller returns a ChallengeResult action.
  3. The middleware intercepts the ChallengeResult and creates a 302 response, which redirects the user to the Azure AD sign-in page.
  4. The user authenticates with Azure AD.
  5. Azure AD sends an ID token to the application.
  6. The middleware validates the ID token. At this point, the user is now authenticated inside the application.
  7. The middleware redirects the user back to application.

The following diagram shows the authentication process flow.

sign-in-flow

* IDP – Identity Provider

  1. The user clicks the “Sign in” button, and the browser sends a GET request. For example: GET /Account/SignIn/.
  2. The account controller returns a ChallengeResult.
  3. The OIDC middleware returns an HTTP 302 response, redirecting to Azure AD.
  4. The browser sends the authentication request to Azure AD
  5. The user signs in to Azure AD, and Azure AD sends back an authentication response.
  6. The OIDC middleware creates a claims principal and passes it to the Cookie Authentication middleware.
  7. The cookie middleware serializes the claims principal and sets a cookie.
  8. The OIDC middleware redirects to the application’s callback URL.
  9. The browser follows the redirect, sending the cookie in the request.
  10. The cookie middleware deserializes the cookie to a claims principal and sets HttpContext.User equal to the claims principal. The request is routed to an MVC controller.

I hope you followed the article. If you have any comments, questions or suggestions, leave a message and I will try to respond at my earliest.