Sunday 11 May 2014

Implementing Azure AD Group Authorization

In my last article we talked about implementing AD single sign-on authentication to an MVC5 website, now we're going to look at adding AD group authorization to a controller with a customised AuthorizeAttribute implementation. Azure AD doesn’t currently allow addition or custom roles, there are a number of built-in administrator roles; however we have full control over groups so we can use these for authorization.

Unfortunately authorization isn’t as simple as just using the Authorize attribute with a role as you would with ASP.Net roles, we need to query the Azure AD Graph API to check if a user is a member of  the group. We’ll add a Sales group to the AzureBakery AD we created in the last article and then implement a custom AuthorizeAttribute to query the Azure AD Graph API using the Azure AD Graph client.

This was written for an MVC controller but can be used for a Web API controller and could used with Azure Mobile Services too.

We’re going to use the Azure AD PowerShell module to modify the AD Application service principal later in the procedure, so install this first.
You can download the module from here:

I needed to install Microsoft Online Services Sign-In Assistant for IT Professionals BETA (not RTW): http://www.microsoft.com/en-us/download/confirmation.aspx?id=39267

Creating an AD Group

1.      First go the AD GROUP workspace in the portal for our Active Directory and click ADD GROUP on the toolbar:


2.      Enter a name and description of the group and click the tick button to create it:


3.      Next click on the newly created group and then ADD MEMBERS on the toolbar:


4.      In the Add members dialog, click on the AD user we created to add it to the SELECTED list and click the tick button to confirm:


5.      Now go to the GROUP CONFIGURE tab and make a note of the OBJECT ID, we’ll need this later.
6.      Next we need to create a key for our application to allow us to access the graph API, so create a new key in the APPLICATION workspace CONFIGURE tab:


7.      Make a note of this and the CLIENT ID.
8.      We need to create keys for the local and Azure AD applications.

Modifying the Application Service Principal

We need to modify our application’s Service Principal so that it has permission to access the Graph API, in theory this should be done by adjusting the permissions to other applications section of the APPLICATION CONFIGURATION tab but at the time of writing this it doesn’t work. Please try it yourself and if it doesn’t work for you (you will get an unauthorized exception in the AD Graph API Client) use the following procedure to manually add the service principal to an administrator role:

1.      Launch the Azure AD PowerShell Console (from the desktop shortcut if you chose to use it).
2.      First we need to obtain our AD credentials, so type the following command and enter your AD user credentials when prompted:

$msolcred = get-credential

This stores the credentials in a variable called $msolcred.

3.      Next we need to connect the console by typing the following command:

connect-msolservice -credential $msolcred

For a quick test, we can use the get-msoluser command to list AD users. We should see something like this:

PS C:\WINDOWS\system32> get-msoluser

UserPrincipalName          DisplayName                isLicensed
-----------------          -----------                ----------
gwebbercross_outlook.co... Geoff Webber-Cross         False
geoff@azurebakery.onmic... Geoff                      False

4.      Now we need to get the service principal for our application using the following command (get the CLIENT ID from CONFIGURE tab of the AD APPLICATION workspace for the application associated with the website):


$msolServicePrincipal = Get-MsolServicePrincipal -AppPrincipalId YourClientId

5.      We can see the properties  of the service principal object by outputting it like this:
write-output $msolServicePrincipal
6.      Next we need to add the service principal to an administrator role like this:

            Add-MsolRoleMember -RoleName “Company Administrator” -RoleMemberObjectId $msolServicePrincipal.ObjectId –RoleMemberType ServicePrincipal

Implementing AzureAdAuthorizeAttribute

We’re going to create a class called AzureAdAuthorizeAttribute which can be added to a controller with either a group name or group ObjectId specified. The ObjectId implementation is more efficient as it doesn’t require an additional query to look up the id from the name.
We need to install the Microsoft.Azure.ActiveDirectory.GraphClient and Microsoft.IdentityModel.Clients.ActiveDirectory Microsoft.IdentityModel.Clients.ActiveDirectory NuGet packages by entering the following commands:

  • Install-Package Microsoft.Azure.ActiveDirectory.GraphClient
  • Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory


This is the complete code for the attribute, the comments in the code explain what’s going on:

using Microsoft.Azure.ActiveDirectory.GraphClient;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Security.Claims;
using System.Web;
using System.Web.Mvc;

namespace AdminWebsite.Auth
{
    [AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public class AzureAdAuthorizeAttribute : AuthorizeAttribute
    {
        private readonly string _clientId = null;
        private readonly string _appKey = null;
        private readonly string _graphResourceID = "https://graph.windows.net";


        public string AdGroup { get; set; }
        public string AdGroupObjectId { get; set; }

        public AzureAdAuthorizeAttribute()
        {
            this._clientId = ConfigurationManager.AppSettings["ida:ClientID"];
            this._appKey = ConfigurationManager.AppSettings["ida:Password"];
        }
       
        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            // First check if user is authenticated
            if (!ClaimsPrincipal.Current.Identity.IsAuthenticated)
                return false;
            else if (this.AdGroup == null && this.AdGroupObjectId == null) // If there are no groups return here
                return base.AuthorizeCore(httpContext);
           
            // Now check if user is in group by querying Azure AD Graph API using client
            bool inGroup = false;
           
            try
            {
                // Get information from user claim
                string signedInUserId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
                string tenantId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
                string userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;


                // Get AuthenticationResult for access token
                var clientCred = new ClientCredential(_clientId, _appKey);
                var authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}", tenantId));
                var authResult = authContext.AcquireToken(_graphResourceID, clientCred);
                
                // Create graph connection with our access token and API version
                var currentCallContext = new CallContext(authResult.AccessToken, Guid.NewGuid(), "2013-11-08");
                var graphConnection = new GraphConnection(currentCallContext);

                // If we don't have a group id, we can quiery the graph API to find it
                if (this.AdGroupObjectId == null)
                {
                    // Get all groups
                    var groups = graphConnection.List<Group>(null, null);

                    if (groups != null && groups.Results != null)
                    {
                        // Find group object
                        var group = groups.Results.SingleOrDefault(r => (r as Group).DisplayName == this.AdGroup);


                        // check if user is in group
                        if (group != null)
                            this.AdGroupObjectId = group.ObjectId;
                    }
                }

                if (this.AdGroupObjectId != null)
                    inGroup = graphConnection.IsMemberOf(this.AdGroupObjectId, userObjectId);
            }
            catch(Exception ex)
            {
                string message = string.Format("Unable to authorize AD user: {0} against group: {1}", ClaimsPrincipal.Current.Identity.Name, this.AdGroup);
               
                throw new Exception(message, ex);
            }

            return inGroup;
        }
    }
}

Once we’ve created this class, we need to add the ida:ClientID and ida:Password settings to the web.config like this:

  <appSettings>
    <add key="ida:ClientID" value="d30553b1-21f3-4ee5-bda5-63cf9b2d9861" />
    <add key="ida:Password" value="60VVjSMWB5IHNtfIBym9eIv7XXXXXXXXXXXXXXXXXXXXXXXXXX=" />
  </appSettings>

Once we’ve done this, we can simply add the attribute to our controllers to implement Azure AD Group authorization like this:

namespace AdminWebsite.Controllers
{
    [AzureAdAuthorize(AdGroup = "Sales", AdGroupObjectId = "f8a96bf1-c152-41a8-abcdefabcdef")]
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

So that we can automatically switch the web.config settings for the Azure web application, we can simply add the following transform to web.Release.config which will be run during publishing:

<appSettings>
      <add key="ida:ClientID" value="123456-58a2-4549-95fc-AABBCCDDee" xdt:Transform="SetAttributes" xdt:Locator="Match(key)" />
      <add key="ida:Password" value="dXqblNwq1y//qOsgI3mD69KfxIFNfXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=" xdt:Transform="SetAttributes" xdt:Locator="Match(key)" />
    </appSettings>

  <system.web>

No comments:

Post a Comment