Adding Azure AD Authentication on existing MVC applications…

What happens if you’ve already been working on MVC and wanted to add Active Directory Auth for reasons such as “Hey, we just enabled Azure AD” or “Let’s use Azure AD instead of Forms Auth, etc…”. Well, chances are you’ll need to create a new project by following this article: “Developing ASP.NET Apps with Windows Azure Active Directory” then copy over the items that’s specific to AD auth into your application. Essentially, here’s the same guide but saving you the hassle of identifying the changes and/or additions added by the new project creation wizard. Also, this focuses on WSFED (Web Security Federation) Azure AD authentication.

Prerequisites:

  • Azure Active Directory configured for your account or organization. If you haven’t done this yet or just need to get more insights on Azure AD, strongly suggest going through this site.
  • MVC App (at least for this example)

The first step is to create an Application in Azure Active Directory for your website to use to authenticate AD users. To do this:

    • Sign in to the Azure Management Portal (http://azure.microsoft.com).
    • Click on the Active Directory icon on the left menu, and then click on the desired directory.
    • On the top menu, click Applications. If no apps have been added to your directory, this page will only show the Add an App link. Click on the link, or alternatively you can click on the Add button on the command bar.
    • On the What do you want to do page, click on the link to Add an application my organization is developing.
    • On the Tell us about your application page, you must specify a name for your application as well as indicate the type of application you are registering with Azure AD. You can choose from a web application and/or web API (default) or native client application which represents an application that is installed on a device such as a phone or computer. For this guide, make sure to select Web Application and/or Web AP
    • Once finished, click the arrow icon on the bottom-right corner of the page
    • On the App properties page, provide the Sign-on URL (URL for your web application) and App ID URI (Unique URI for your application – Usually it’s a combination or your AD domain/application. Example: http://www.domain.com/mywebsite.somedomain.com) for your web application then click the checkbox in the bottom-right hand corner of the page.
    • Your application has been added, and you will be taken to the Quick Start page for your application.
    • Click on the “Configure” Tab. Generate a Key for your client access and write down the following information:

CLIENT ID:
CLIENT KEY: (You generate a Key by clicking on the Save Button on the configure tab)
APP ID URI:
Federation Metadata Document: (You can get this information by click on “VIEW ENDPOINTS” at the bottom section of the Configure tab

Capture

Time to modify your app for AD Auth! Let’s start by adding assembly references to your MVC Application. Using Nuget Manager, install the following Nuget Packages:

      • Active Directory Authentication Library
      • Entity Framework (this Is used to store WSFed Thumbprints)
      • Microsoft ASP.Net Identity EntityFramework
      • Microsoft ASP.Net Identity Core
      • Microsoft ASP.Net Web Optimization Framework
      • Microsoft Token Validation Extension for Microsoft .Net Framework 4.5

Add 3 Model Classes to handle obtaining thumbprints (WSFed) and User Profile

public class IssuingAuthorityKey
    {
        public string Id { get; set; }
    }

    public class Tenant
    {
        public string Id { get; set; }
    }
public class UserProfile
    {public string DisplayName { get; set; }
        public string GivenName { get; set; }
        public string Surname { get; set; }
    }

Add a DBContext Class (Entity Framework) to handle the DB lookup for the WSFED Tenant and Thumbprints:

public class TenantDbContext : DbContext
    {
        public TenantDbContext()
            : base("AuthorizeDB")
        {
        }

        public DbSet<IssuingAuthorityKey> IssuingAuthorityKeys { get; set; }

        public DbSet<Tenant> Tenants { get; set; }
    }

Add The DatabaseIssuerNameRegistry utility Class (This will handle the DB Lookup for Tenant ID and Thumbprint)

namespace MyNamespace.Utils
{
    public class DatabaseIssuerNameRegistry : ValidatingIssuerNameRegistry
    {
        public static bool ContainsTenant(string tenantId)
        {
            using (TenantDbContext context = new TenantDbContext())
            {
                return context.Tenants
                    .Where(tenant => tenant.Id == tenantId)
                    .Any();
            }
        }

        public static bool ContainsKey(string thumbprint)
        {
            using (TenantDbContext context = new TenantDbContext())
            {
                return context.IssuingAuthorityKeys
                    .Where(key => key.Id == thumbprint)
                    .Any();
            }
        }

        public static void RefreshKeys(string metadataLocation)
        {
            IssuingAuthority issuingAuthority = ValidatingIssuerNameRegistry.GetIssuingAuthority(metadataLocation);

            bool newKeys = false;
            bool refreshTenant = false;
            foreach (string thumbprint in issuingAuthority.Thumbprints)
            {
                if (!ContainsKey(thumbprint))
                {
                    newKeys = true;
                    refreshTenant = true;
                    break;
                }
            }

            foreach (string issuer in issuingAuthority.Issuers)
            {
                if (!ContainsTenant(GetIssuerId(issuer)))
                {
                    refreshTenant = true;
                    break;
                }
            }

            if (newKeys || refreshTenant)
            {
                using (TenantDbContext context = new TenantDbContext())
                {
                    if (newKeys)
                    {
                        context.IssuingAuthorityKeys.RemoveRange(context.IssuingAuthorityKeys);
                        foreach (string thumbprint in issuingAuthority.Thumbprints)
                        {
                            context.IssuingAuthorityKeys.Add(new IssuingAuthorityKey { Id = thumbprint });
                        }
                    }

                    if (refreshTenant)
                    {
                        foreach (string issuer in issuingAuthority.Issuers)
                        {
                            string issuerId = GetIssuerId(issuer);
                            if (!ContainsTenant(issuerId))
                            {
                                context.Tenants.Add(new Tenant { Id = issuerId });
                            }
                        }
                    }
                    context.SaveChanges();
                }
            }
        }

        private static string GetIssuerId(string issuer)
        {
            return issuer.TrimEnd('/').Split('/').Last();
        }

        protected override bool IsThumbprintValid(string thumbprint, string issuer)
        {
            return ContainsTenant(GetIssuerId(issuer))
                && ContainsKey(thumbprint);
        }
    }
}

Modify your controller class and add the following global variables:

private const string TenantIdClaimType = "http://schemas.microsoft.com/identity/claims/tenantid";
 private const string LoginUrl = "https://login.windows.net/{0}";
 private const string GraphUrl = "https://graph.windows.net";
 private const string GraphUserUrl = "https://graph.windows.net/{0}/users/{1}?api-version=2013-04-05";
 private static readonly string AppPrincipalId = ConfigurationManager.AppSettings["ida:ClientID"];
 private static readonly string AppKey = ConfigurationManager.AppSettings["ida:Password"];

Modify your controller class to include the UserProfile action. This action will handle the work to get user information.

public async Task<ActionResult> UserProfile()
        {
            string tenantId = ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value;

            // Get a token for calling the Windows Azure Active Directory Graph
            AuthenticationContext authContext = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, LoginUrl, tenantId));
            ClientCredential credential = new ClientCredential(AppPrincipalId, AppKey);
            AuthenticationResult assertionCredential = authContext.AcquireToken(GraphUrl, credential);
            string authHeader = assertionCredential.CreateAuthorizationHeader();
            string requestUrl = String.Format(
                CultureInfo.InvariantCulture,
                GraphUserUrl,
                HttpUtility.UrlEncode(tenantId),
                HttpUtility.UrlEncode(User.Identity.Name));

            HttpClient client = new HttpClient();
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
            request.Headers.TryAddWithoutValidation("Authorization", authHeader);
            HttpResponseMessage response = await client.SendAsync(request);
            string responseString = await response.Content.ReadAsStringAsync();
            UserProfile profile = JsonConvert.DeserializeObject<UserProfile>(responseString);

            return View(profile);
        } 

Add a new View for the UserProfile Action

To do this, while inside your Controller class, select UserProfile, right click, and then select “ADD View”
Modify the razorview file with the following:

@model MyNamespace.Models.UserProfile
@{
    ViewBag.Title = "User Profile";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>@ViewBag.Title.</h2>

<table class="table table-bordered table-striped">
    <tr>
        <td>Display Name</td>
        <td>@Model.DisplayName</td>
    </tr>
    <tr>
        <td>First Name</td>
        <td>@Model.GivenName</td>
    </tr>
    <tr>
        <td>Last Name</td>
        <td>@Model.Surname</td>
    </tr>
</table>

Create a new “Action” Controller to handle Sign-Out Action:

namespace MyNamespace.Controllers
{
    public class AccountController : Controller
    {
        public ActionResult SignOut()
        {
            WsFederationConfiguration config = FederatedAuthentication.FederationConfiguration.WsFederationConfiguration;

            // Redirect to SignOutCallback after signing out.
            string callbackUrl = Url.Action("SignOutCallback", "Account", routeValues: null, protocol: Request.Url.Scheme);
            SignOutRequestMessage signoutMessage = new SignOutRequestMessage(new Uri(config.Issuer), callbackUrl);
            signoutMessage.SetParameter("wtrealm", IdentityConfig.Realm ?? config.Realm);
            FederatedAuthentication.SessionAuthenticationModule.SignOut();

            return new RedirectResult(signoutMessage.WriteQueryString());
        }

        public ActionResult SignOutCallback()
        {
            if (Request.IsAuthenticated)
            {
                // Redirect to home page if the user is authenticated.
                return RedirectToAction("Index", "AccessibilityReports");
            }
            return View();
        }
    }
}

Add a new View for the SignOutCallback action

To do this, while inside the “Action” Controller class, select SignOutCallback, right click, then select “ADD View”. Modify the razorview file with the following:

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
  
</head>
<body>
    <div>
        <p class="text-success">You have successfully signed out.</p> 
    </div>
</body>
</html>

Create a Login Partial View (_LoginPartial.cshtml) to show the user information

While in the MVC Project, Navigate to Views then Shared. Right Click on Shared, select “Add Views”. Make sure that the name is _LoginPartialand that the “Create as Partial View” is checked. Modify the razorview file:

@using System
@{
    var user = "Null User";
    if (!String.IsNullOrEmpty(User.Identity.Name))
    {
        user = User.Identity.Name;
    }

}

@if (Request.IsAuthenticated)
{
    <text>
        <ul class="nav navbar-nav navbar-right">
            <li>
                @Html.ActionLink(user, "UserProfile", "<ControllerThatHastheUserProfileAction", routeValues: null, htmlAttributes: null)
            </li>
            <li>
                @Html.ActionLink("Sign out", "SignOut", "Account")
            </li>
        </ul>
    </text>
}
else
{
    <ul class="nav navbar-nav navbar-right">
        <li>@Html.ActionLink("Sign in", "Index", "<ControllerThatHastheUserProfileAction", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
    </ul>
}

Add the Partial View in your Navbar, Header, Sidebar, etc…

Add the partial page in the _layout.cshtml file. This is where you would add your application:

@Html.Partial("_LoginPartial")

Modify your web.config file with WSFED references and AppKey settings:

Appkey Settings:

    <add key="ida:FederationMetadataLocation" value="https://login.windows.net/xxxxxxxxxx<this also your Tenant ID>/federationmetadata/2007-06/federationmetadata.xml" />
    <add key="ida:Realm" value="Your APP ID URI" />
    <add key="ida:AudienceUri" value=" Your APP ID URI " />
    <add key="ida:ClientID" value=" Your CLIENT ID " />
    <add key="ida:Password" value=" Your CLIENT KEY " />

Connection Strings (This DB will contain the Tenant ID and Thumbprint):

connectionStrings>
       <add name="AuthorizeDB" connectionString="Server=XXXX;Database=XXX;User ID=XXXX;Password=XXXX;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" providerName="System.Data.SqlClient" />
  </connectionStrings>

Add the IdentityModel Section:

NOTE: That the issuerNameRegistry type is the full qualified namespace of your DatabaseIssuerNameRegistry class comma your Application name (Project name)

<system.identityModel>
    <identityConfiguration>
      <issuerNameRegistry type="MyNamespace.Utils.DatabaseIssuerNameRegistry, MyApplicationProjectName" />
      <audienceUris>
        <add value="http://alaskaair.com/ITQEToolsSet" />
      </audienceUris>
      <securityTokenHandlers>
        <add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
        <remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      </securityTokenHandlers>
      <certificateValidation certificateValidationMode="None" />
    </identityConfiguration>
  </system.identityModel>

Getting the certificate Thumbprints to insert in your Database:

  1. In a web browser, go to https://manage.windowsazure.com, sign in to your account, and then click on the Active Directory icon from the left menu.
  2. Click on the directory where your application is registered, and then click on the View Endpoints link on the command bar.
  3. From the list of single sign-on and directory endpoints, copy the Federation Metadata Document link.
  4. Open a new tab in your browser, and go to the URL that you just copied. You will see the contents of the Federation Metadata XML document. For more information about this document, see the Federation Metadata topic.
  5. For the purposes of updating an application to use a new key, locate each <RoleDescriptor> block, and then copy the value of each block’s <X509Certificate> element. For example
<RoleDescriptor xmlns:fed="http://docs.oasis-open.org/wsfed/federation/200706" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" protocolSupportEnumeration="http://docs.oasis-open.org/wsfed/federation/200706" xsi:type="fed:SecurityTokenServiceType">
      <KeyDescriptor use="signing">
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <X509Data>
                    <X509Certificate>MIIDPjC…BcXWLAIarZ</X509Certificate>

After you’ve copied the value of the <X509Certificate> element, open a plain text editor and paste the value. Make sure that you remove any trailing whitespace, and then save the file with a .cer extension.

NOTE: Make sure to go through each X509Certificate node and create a .cer. Your Federation Metadata Document may contain 2 or more X509Certificates (thumbprints)

Open each .Cer and copy the thumbprints. Make sure to remove any whitespace in between.

Create the TenantDB in SQL:

GO
CREATE TABLE [dbo].[IssuingAuthorityKeys] (
    [Id] NVARCHAR (128) PRIMARY KEY NOT NULL
);
GO

INSERT INTO [dbo].[IssuingAuthorityKeys]
           ([Id])
     VALUES
           ('replace with your thumbprint from .cer')
GO

INSERT INTO [dbo].[IssuingAuthorityKeys]
           ([Id])
     VALUES
           (' replace with your thumbprint from .cer ')
GO

CREATE TABLE [dbo].[Tenants] (
    [Id] NVARCHAR (128) NOT NULL PRIMARY KEY
);
GO

INSERT INTO [dbo].Tenants
           ([Id])
     VALUES
           ('Your TENANT ID is the GUID Value from your Federation Metadata Document URL')

That’s it! Oops One more thing. Make sure to add the [Authorize] attribute in your class or action or wherever you want your users to be authenticated. In my case, I added this attribute in the class level for all my controllers as I wanted everyone to be authenticated to view all my pages 

[Authorize] 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s