Adopt Cloud and DevOps

Implementing Hybrid Authentication With Azure Active Directory

Learn how to authenticate a new web application with a legacy application, using a shared authentication system.

by Vijay Athuluri

The Challenge

While building an enterprise web application for one of SingleStone’s clients, I needed a way to authenticate the new web application with the legacy application to reuse common resources. Using a shared authentication system, I was able to achieve this and still be productive without re-writing the services in the new web application.

By experimenting with different systems for the client’s legacy apps (mostly Office 365 apps), I most benefited from using Windows Azure Active Directory (AD). Since every Office 365 tenant uses Azure AD, all directory objects reside in Azure AD - which handles authentication in the cloud.

Challenges Resolved Using Azure AD

There are multiple challenges that I faced with the on-prem approach—things like single point of failure, active directory server being too chatty with all other services and more. However, the major challenge that I wanted to resolve was using on-prem identity provider that could work seamlessly with cloud identity provider, essentially creating a hybrid identity management solution.

By choosing Azure AD, I resolved most of these challenges. I took advantage of its rich single sign on features, standard protocols, consistent identity management system, and access resources from different web applications hosted in the cloud. Most importantly, all these web applications can talk seamlessly using graph RESTful api calls which meant that I didn’t have to spend  time learning another technology. 

Here is the 3-step approach I took to resolve these challenges:    

  1. 1. The initial step is to create an Azure AD account/tenant directory. Once you have created the directory you can create users and roles there. Details on how to create your active directory are beyond the scope of this post, but multiple sources of documentation exist.
  2. 2. The second step is to register your web application to that tenant. By doing this, you are telling your Azure AD that you want to use Azure AD as your authentication system for this web application.
  3. 3. The third step is to actually implement the code in your web application by calling the Azure AD graph api using the graph client package.

Each of these steps plays a specific role in how the web application seamlessly integrates with the Azure AD ecosystem.

Each of the steps is explained in further detail below.

Getting Started with Azure AD

To get started with Windows Azure AD, you need to have an active Microsoft Azure subscription.

  1. 1. Sign in to your azure portal account.
  2. 2. Search for active directory.
  3. Azure Active Directory

  4. 3. At this moment you can only access Azure AD via the old portal. Navigate to active directory in old portal.
  5. Active Directory in Old Portal

  6. 4. Click on “create your directory”. This will launch the create directory form.
  7. Create Directory Form

  8. 5. Create your directory.
    • Directory - Choose whether to create a new directory, or grant your Microsoft account permission to manage an existing Microsoft Azure AD directory for which you are a global administrator.
    • Name - Enter the name of the directory.
    • Domain name - Choose a domain name that you want the directory to be on.
    • Region – Choose the geographic region closest to where your application is hosted to reduce authentication latency.
  9. 6. Once the provisioning of your directory is complete, you will be able to see it on your active directory listing page.
  10. 7. You can manage the directory by clicking on the directory you just created.
  11. 8. Each tab allows you to perform a particular set of management as follows:
    • Users - Create and Manage cloud-based users.
    • Groups - Create and Manage groups of users.
    • Applications - Integrate over 580+ 3rd Party Commercial SaaS Applications and your own cloud-based applications with Windows Azure Active Directory.
    • Domains – Add a custom DNS domain name.
    • Directory Integration - Configure integration with an on-premise Windows Server Active Directory forest.
    • Configure - Enable Active Directory Premium features and Multi-Factor Authentication.
    • Reports - Access security reports, such as anomaly reports and resource usage reports.

Register Your WebApp in Your Own Tenant

  1. 1. Login to your Azure portal account and got to Azure AD tab.
  2. 2. Select your tenant directory to add your sample web application.
  3. 3. Click on the application tab in your directory.
  4. Application Tab

  5. 4. Select “add application my organization is developing”.
  6. 5. Enter your application name and select “WEB APPLICATION AND/OR WEB API”.
  7. 6. For sign-on URL, enter your localhost URL. This normally looks like https://contoso.azurewebsite.net/
  8. 7. For app URL enter https://contosoorganization/contoso  where contosoorganization is your organization tenant name.
  9. 8. Click on “configure tab”, copy your client ID and secret key information. Refer to this URL on how to get your secret key: https://msdn.microsoft.com/en-us/office/office365/howto/add-common-consent-manually
  10. 9. Configure permissions.
  11. 10. Click save.

Integrate Azure AD Into Web Applications Using Graph API

The Azure AD Graph API provides programmatic access to Azure AD through OData REST API endpoints. Applications can use the Graph API to perform create, read, update, and delete (CRUD) operations on directory data and objects.

Azure AD makes it simple to delegate your web app authentication (identity management) efficiently. With the latest asp.net framework you can accomplish this by OWIN middleware implementation.

  1. 1. Create a new mvc web app using vs 2013.
  2. Create a new mvc web app using vs 2013.

  3. 2. Change authentication to no authentication.
  4. Change authentication to no authentication.

  5. 3. Set SSL Enable to true and copy the ssl url. We are going to use this url to configure the Azure AD application in our Azure portal.
  6. Set SSL Enable to true and copy the ssl url.

  7. 4. Add the following nugget packets. These are helpful nugget packages that will provide assembly classes that will extend the functionality of base .net framework. The first package in the list will extend the functionality of identitymodel to use openId connect and WS-federation protocols. Jwt pkg will provide support for JWT tokens. The other owin middleware packages enable our application to support any standard OAuth2.0 authentication workflow.

     

    a. PM> Install-Package Microsoft.IdentityModel.Protocol.Extensions
    b. PM> Install-Package System.IdentityModel.Tokens.Jwt
    c. PM> Install-Package Microsoft.Owin.Security.OpenIdConnect
    d. PM> Install-Package Microsoft.Owin.Security.Cookies
    e. PM> Install-Package Microsoft.Owin.Host.SystemWeb
     

     

  8. 5. Add the stable version of the directory authentication library.
  9. Add the stable version of the directory authentication library.

  10. 6. Install graph api client. This package will enable the support for javascript graph api client library.
  11.  

    a. PM> Install-Package Microsoft.Azure.ActiveDirectory.GraphClient -Version 1.0.3
     

     

  12. 7. Create a class in app_start folder startup.auth.cs and replace the code with the sample app. This will add the functionality to the authentication of the app by using OpenIdConnection.
  13. 8. Create a OWIN start class in the project. Right click on the project and select OWIN Startup Class. If you don’t see that in the menu, select class and search for OWIN in the search bar. Add to the project and name it Startup.cs
  14. Add to the project and name it Startup.cs

  15. 9. Replace the code in startup with this code below. This will allow the app on startup to use the configuration that we wrote in startup.auth.cs.
  16.  

    public partial class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                ConfigureAuth(app);
            }
        }
     

     

  17. 10. Create a new view in the view share folder called _LoginPartial.cshtml. Replace the code with the below code.
  18.  Login.cshtml

  19. 11. Replace the code in _layout.cshtml with the below code.
  20.  _layout.cshtml

  21. 12. In the models folder add a class called UserProfile.cs. Replace the code with below code.
  22.  

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

     

  23. 13. Create a new folder in the project called Utils and create 3 classes and replace the code.
    • NaiveSessionCache.cs
  24.  

    public class NaiveSessionCache : TokenCache
        {
            private static readonly object FileLock = new object();
            private readonly string CacheId = string.Empty;
            private string UserObjectId = string.Empty;

            public NaiveSessionCache(string userId)
            {
                UserObjectId = userId;
                CacheId = UserObjectId + "_TokenCache";

                AfterAccess = AfterAccessNotification;
                BeforeAccess = BeforeAccessNotification;
                Load();
            }

            public void Load()
            {
                lock (FileLock)
                {
                    if (HttpContext.Current != null)
                    {
                        Deserialize((byte[]) HttpContext.Current.Session[CacheId]);
                    }
                }
            }

            public void Persist()
            {
                lock (FileLock)
                {
                    // reflect changes in the persistent store
                    HttpContext.Current.Session[CacheId] = Serialize();
                    // once the write operation took place, restore the HasStateChanged bit to false
                    HasStateChanged = false;
                }
            }

            // Empties the persistent store.
            public override void Clear()
            {
                base.Clear();
                HttpContext.Current.Session.Remove(CacheId);
            }

            public override void DeleteItem(TokenCacheItem item)
            {
                base.DeleteItem(item);
                Persist();
            }

            // Triggered right before ADAL needs to access the cache.
            // Reload the cache from the persistent store in case it changed since the last access.
            private void BeforeAccessNotification(TokenCacheNotificationArgs args)
            {
                Load();
            }

            // Triggered right after ADAL accessed the cache.
            private void AfterAccessNotification(TokenCacheNotificationArgs args)
            {
                // if the access operation resulted in a cache update
                if (HasStateChanged)
                {
                    Persist();
                }
            }
        }
     

     

    • GraphConfiguration.cs

     

    internal static class GraphConfiguration
        {
            private const string tenantIdClaimType = "http://schemas.microsoft.com/identity/claims/tenantid";
            private static string graphResourceId = ConfigurationManager.AppSettings["ida:GraphUrl"];
            private static string graphApiVersion = ConfigurationManager.AppSettings["ida:GraphApiVersion"];

            internal static string GraphApiVersion
            {
                get { return graphApiVersion; }
                set { graphApiVersion = value; }
            }

            internal static string GraphResourceId
            {
                get { return graphResourceId; }
                set { graphResourceId = value; }
            }

            internal static string TenantIdClaimType
            {
                get { return tenantIdClaimType; }
            }
        }
     

     

    • Logger.cs

     

    public class Logger
        {
            ///
            ///     Log errors and exceptions.
            ///

            /// Formatted message.
            /// Message arguments.
            public static void Error(string message, params object[] args)
            {
                Trace.TraceError(message, args);
                Console.WriteLine(message, args);
            }

            ///
            ///     Log warnings.
            ///

            /// Formatted message.
            /// Message arguments.
            public static void Warning(string message, params object[] args)
            {
                Trace.TraceWarning(message, args);
                Console.WriteLine(message, args);
            }

            ///
            ///     Log information.
            ///

            /// Formatted message.
            /// Message arguments.
            public static void Info(string message, params object[] args)
            {
                Trace.TraceInformation(message, args);
                Console.WriteLine(message, args);
            }
        }
     

     

  25. 14. Add an mvc account controller and replace the code with below code.
  26.  

    public class AccountController : Controller
        {
            public void SignIn()
            {
                // Send an OpenID Connect sign-in request.
                if (!Request.IsAuthenticated)
                {
                    HttpContext.GetOwinContext()
                        .Authentication.Challenge(new AuthenticationProperties {RedirectUri = "/"},
                            OpenIdConnectAuthenticationDefaults.AuthenticationType);
                }
            }

            public void SignOut()
            {
                // Remove all cache entries for this user and send an OpenID Connect sign-out request.
                string userObjectID =
                    ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
                AuthenticationContext authContext = new AuthenticationContext(Startup.Authority,
                    new NaiveSessionCache(userObjectID));
                authContext.TokenCache.Clear();
                AuthenticationHelper.token = null;
                HttpContext.GetOwinContext().Authentication.SignOut(
                    OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
            }
        }
     

     

  27. 15. In web.config, in , create keys for ida:ClientId, ida:AppKey, ida:AADInstance, ida:Tenant,ida:PostLogoutRedirectUri, ida:GraphApiVersion, and ida:GraphUrl and set the values accordingly. For the public Azure AD, the value of ida:AADInstance is https://login.windows.net/{0}, the value of ida:GraphResourceId is https://graph.windows.net, and the value of ida:GraphUrl is https://graph.windows.net/.
  28. 16. In web.config add this line in the section: . This increases the ASP.Net session state timeout to its maximum value so that access tokens and refresh tokens cache in session state aren't cleared after the default timeout of 20 minutes.
  29. 17. Now Run the application with your own Tenant admin.

That’s it. You can now run your web application by simply pressing function F5.

Learn more about our DevOps and Cloud service offering.

Vijay Athuluri
Vijay Athuluri
Senior Consultant
Contact Vijay

Related Articles