Using Windows Azure Active Directory as an Attribute Store in AD FS

More and more of our customers are unleashing the power of Windows Azure Active Directory. This Enterprise-level identity and access management for the cloud is so powerful that most like, I'll be creating more articles on this subject.

When you add Azure Active Directory as an Identity Provider, or Claims Provider, in Active Directory Federation Services, you get a defined set of claims from it. If you're not using the Premium version of Azure Active Directory, you won't for example get claims for group membership in Azure Active Directory. Other attributes that might be present in Azure Active Directory, like an alternate email address, are also not available as claims. There could be occasions where you need this information when a user authentications through an Azure Active Directory that you manage. (Now that is the keyword here; whatever we do in this article requires you to have proper permissions on the Azure Active Directory.)

In order to use attributes from Azure Active Directory users as claims in AD FS, we can create an Attribute Store that queries Azure Active Directory. This article explains just that. To get started, first create you Attribute Store as outlined on one of my earlier articles; How to create a Custom Attribute Store for Active Directory Federation Services 3.0. We'll focus on the code that you need to write in the class that you create.

Prerequisites

Active Directory Authentication Library

First, we need to add the Active Directory Authentication Library. This is a NuGet package that can be installed by using the Package Manager Console (at the bottom of you Visual Studio screen) or through the Tools, Package Manager and then either Package Manager Console or Manage NuGet Packages for Solution...

If you're using the Package Manager Console type this command:

Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -Version 1.0.3

In this case, I'm adding version 1.0.3, but newer versions might be available. Please check https://www.nuget.org/packages/Microsoft.IdentityModel.Clients.ActiveDirectory/ to see what the latest version is.

If you're using the Manage NuGet Packages option, search for "Active Directory Authentication Library" and select the version you want from there.

After adding that NuGet package, download the Windows Azure AD Graph API Helper Library. Unpack the zip file, open the Solution in Visual Studio and compile it. Next, add a reference to the resulting DLL from you Attribute Store project. Now, the references in you Attribute Store project should resemble this;

Building the code

Now switch to the class file where you will build the Attribute Store in. In the example I referenced earlier, it's called Class1.cs, but I will give it a more meaningful name, like WAADAttributeStore.cs

Inspect the using statements, at the beginning of the code, and add these lines:

 using Microsoft.IdentityServer.ClaimsPolicy.Engine.AttributeStore;
using Microsoft.WindowsAzure.ActiveDirectory;
using Microsoft.WindowsAzure.ActiveDirectory.GraphHelper;
using System.IdentityModel;

Make sure you properly paste this code as the Initialize method in the class:

         public void Initialize(Dictionary<string, string> config)
        {
            if (null == config)
            {
                throw new AttributeStoreInvalidConfigurationException("No configuration parameters passed.");
            }

            string tenantName;
            string clientId;
            string password;

            if (config.ContainsKey("TenantName"))
            {
                tenantName = config["TenantName"];
            }
            else
            {
                throw new AttributeStoreInvalidConfigurationException("TenantName configuration parameter not found.");
            }
            if (config.ContainsKey("ClientId"))
            {
                clientId = config["ClientId"];
            }
            else
            {
                throw new AttributeStoreInvalidConfigurationException("ClientId configuration parameter not found.");
            }
            if (config.ContainsKey("Password"))
            {
                password = config["Password"];
            }
            else
            {
                throw new AttributeStoreInvalidConfigurationException("Password configuration parameter not found.");
            }

            AADJWTToken token = DirectoryDataServiceAuthorizationHelper.GetAuthorizationToken(tenantName, clientId, password);
            this.graphService = new DirectoryDataService(tenantName, token);
        }

This Initialize method takes three parameters; TenantName, ClientId and Password. This information can be taken from the Azure management portal at https://manage.windowsazure.com after adding a custom application to your Azure Active Directory. (More information: https://msdn.microsoft.com/en-us/library/dn151791.aspx) Now, let's just finish the coding part. In this example, we going to extract group membership information from Azure Active Directory (which is not required if you use Azure Active Directory Premium, because group membership is available as claims in there).

Replace the BeginExecuteQuery method with this code:

         public IAsyncResult BeginExecuteQuery(string query, string[] parameters, AsyncCallback callback, object state)
        {
            if (String.IsNullOrEmpty(query))
            {
                throw new AttributeStoreQueryFormatException("No query string.");
            }
            if (null == parameters)
            {
                throw new AttributeStoreQueryFormatException("No query parameter.");
            }

            if (parameters.Length != 1)
            {
                throw new AttributeStoreQueryFormatException("More than one query parameter.");
            }

            string inputString = parameters[0];

            if (inputString == null)
            {
                throw new AttributeStoreQueryFormatException("Query parameter cannot be null.");
            }

            string[][] outputValues;

            switch (query)
            {
                case "GetGroupNamesByUserName":
                    {
                        string userPrincipalName = inputString;

                        User user = graphService.users
                            .Where(u => u.accountEnabled == true && u.userPrincipalName == userPrincipalName)
                            .AsEnumerable()
                            .SingleOrDefault();

                        var groupReferences = graphService.LoadProperty(user, "memberOf")
                            .OfType<Group>()
                            .Select(g => g.displayName)
                            .ToArray();

                        outputValues = new string[groupReferences.Length][];

                        for (int i = 0; i < groupReferences.Length; i++)
                        {
                            outputValues[i] = new string[1] { groupReferences[i] };
                        }
                        break;
                    }
                default:
                    {
                        throw new AttributeStoreQueryFormatException("The query string is not supported.");
                    }
            }

            TypedAsyncResult<string[][]> asyncResult = new TypedAsyncResult<string[][]>(callback, state);
            asyncResult.Complete(outputValues, true);
            return asyncResult;
        }

Our sample Attribute Store only supports one single query; GetGroupNamesByUserName. This method gets the users group membership, based on the username (where the username is a UPN). This UPN has to be provided by AD FS (and was originally provided by Azure Active Directory). We then take this UPN to lookup the user in Azure Active Directory, get the group membership and return these groups as claims. You can have AD FS decide what the exact claim type will be that AD FS will issue.

Last, check that the EndExecuteQuery method has the code we've seen before:

         public string[][] EndExecuteQuery(IAsyncResult result)
        {
            return TypedAsyncResult<string[][]>.End(result);
        }

Okay, that's it! Compile it, ship the resulting DLL to the AD FS server, add the Attribute Store and use these initialization parameters:

  • TenantName
  • ClientId
  • Password

Then, configure a claim rule for a relying party to use the GetGroupNamesByUserName query for the attribute store, and pass a UPN as the parameter. Again, if you need more information on how to do this; consult my previous article; How to create a Custom Attribute Store for Active Directory Federation Services 3.0

Have fun!