Monday, August 17, 2015

Accessing Azure AD information from .Net Azure Mobile Services and the Xamarin Client

I've been busy, really busy of late but recently I encountered an interesting problem while working on a while paper comparing Xamarin with PhoneGap. I wanted to back this all by Azure Mobile Services and also use Azure AD for authentication. There are lots of articles out there that explain exactly how to tie the two together and it worked great. This is one such article that is very useful: Authenticate your app with Active Directory Authentication Library Single Sign-On.

The instructions in there are simple enough to follow and getting this to work in my Android application is similarly simple. But when I authenticate, what exactly is it that I get back? Take the following code called in my mobile application:

var token = await service.LoginAsync(this, MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory);

When I look at the token object I find that it contains two properties; MobileServiceAuthenticationToken and UserId. Neither of these looks like anything but binary goo. That's fine, but I want to show the user of the application things like their name stored in AD, AD id, find out what roles they are in and do things like find all the users in a role. How do I do this with .Net Azure Mobile Service backed by Azure AD?

First I found some articles that gave me some clues. Nick's .Net Travels talked about doing this with the Azure AD Graph API. and this is fine but he used the ActiveDirectoryClient class. This worked great for me but I noticed that some of the versions of the required dependencies of Microsoft Azure Active Directory (Microsoft.Azure.ActiveDirectory.GraphClient) Nuget package were of a different version than is required by Microsoft Azure Mobile Services .NET (WindowsAzure.MobileServices.Backend.Entity) version 1.0.470. As of the time of this writing Microsoft Azure Mobile Services .NET, which I needed for my .Net Azure Mobile Service backend, had some dependencies such as one on System.spacial version 5.6.2 and only version 5.6.2. The current version of Microsoft.Azure.ActiveDirectory.GraphClient uses a minimum version of 5.6.4. I tried updating the version that the .Net Azure Mobile Services project used under the hood to 5.6.4 but eventually my Azure Mobile Service stopped working with an error complaining about the version 5.6.2 not being installed. I then found this article on Nick's .Net Travels that illustrated the same problem.

The upshot of the deal, You cannot use any version of the Microsoft.Azure.ActiveDirectory.GraphClient library that uses the ActiveDirectoryClient class introduced in version 2.0.0. Instead you must use version 1.0.3 or earlier of the Nuget package. I ended up editing the Packages.config and project files manually to point to this older version. Since I can't use the ActiveDirectoryClient class, I'm stuck using the older GraphConnection class. With all that in mind, let's look at how I solved two common scenarios using a .Net back end of Azure Mobile Services authenticating with Azure AD.

Problem 1: I want to get information about the logged in user other than the binary parts of the token I get back from the login request.

I want to show the user their AD id or their full names after having logged in, but none of that is returned from the Azure Mobile Services client in Xamarin Android or iOS. I don't want the client trying to hit Azure AD directly so in my .Net Windows Azure Mobile service project I create a new ProfileController that inherits from ApiController to get the currently logged in user's profile information from Active Directory:

[AuthorizeLevel(AuthorizationLevel.User)]
[RoutePrefix("api/Profile")]
public class ProfileController : ApiController
{
    // GET api/Profile
    public async Task<UserProfile> Get()
    {
        var authenticationResult = await Utils.AdIntegration.GetAuthenticationTokenAsync();
        var credentials = await Utils.AdIntegration.GetCredentialsAsync(User);
        var graphConnection = new GraphConnection(authenticationResult.AccessToken);
        var adUser = graphConnection.Get<User>(credentials.ObjectId);
        var groups = graphConnection.GetLinkedObjects(adUser, LinkProperty.MemberOf, null, -1);

       var returnValue = new UserProfile
       {
            FullName = adUser.DisplayName,
            FirstName = adUser.GivenName,
            LastName = adUser.Surname,
            UserId = adUser.UserPrincipalName,
            Manager = groups.Results.ToList().Any(g => g.ObjectId == CloudConfigurationManager.GetSetting(Constants.ConfigurationKeys.ManagerAdGroup))
        };
        return returnValue;
    }
}

Some things to notice off the top, the line [AuthorizeLevel(AuthorizationLevel.User)] decorating the class locks this class down so only logged in users can call the get method. It is marked async because I'm about to make asynchronous I/O calls out to get information from Azure AD.

First thing I did was get the Authentication token for the currently logged on user.  To do this I implemented the following GetAuthenticationTokenAsync method:

public async static Task<AuthenticationResult> GetAuthenticationTokenAsync()
{
    ClientCredential clientCred = new ClientCredential(CloudConfigurationManager.GetSetting(Constants.ConfigurationKeys.ClientId), CloudConfigurationManager.GetSetting(Constants.ConfigurationKeys.ClientSecret));
    AuthenticationContext authenticationContext = new AuthenticationContext(string.Format(CloudConfigurationManager.GetSetting(Constants.ConfigurationKeys.Authority), CloudConfigurationManager.GetSetting(Constants.ConfigurationKeys.AdSite)), false);
    return await authenticationContext.AcquireTokenAsync(Constants.AdIntegration.Graph, clientCred);
}

The Azure AZ class's ClientCredential takes as constructors the client id and client secret you created when connecting your Azure Mobile Services Account to Azure AD. In my example I'm storing these in the Azure Mobile Service's configuration. With that I get the authentication context using the Authority and AD site name setup for Azure AD. In my case it was: https://login.windows.net/{0} and testincidentqueue.onMicrosoft.com. Finally I returned the token from the AcquireTokenAsync method on the authentication context.

With my token in hand, I now needed to get my credentials. To do that I called the GetCredientalsAsync Method I created. This method appears as the following:

public async static Task<AzureActiveDirectoryCredentials> GetCredentialsAsync(IPrincipal user)
{
    var serviceUser = (ServiceUser)user;
    var identities = await serviceUser.GetIdentitiesAsync();
    return (identities != null && identities.Count > 0) ? (AzureActiveDirectoryCredentials)identities[0] : null;
}


This method takes the ApiController's User object for the currently logged in user as a property and casts it into a ServiceUser. If the user is logged into Azure Mobile Services using Azure AD, this should always be a valid cast. With this object I return the first identity found in the GetIdentitiesAsync method, there should only be one.

With the credentials I now have the access token of the current user and with the identity I have the user's AD object Id that I can query against. I use these to tell the AD graph that I want the user with the Id I returned from the identity.  I also query the linked objects of that returned AD user to find out what AD groups this user is a member of. The rest is just returning the results to the caller.

From my client I can now make the following call to my mobile services to get information about the logged in user in Azure AD:

var profile = await service.InvokeApiAsync<UserProfile>("Profile", HttpMethod.Get, null);

Problem 2: I want to get information about all the users in the system who belong to a particular AD group.

This is a similar problem to what I solved before. However, in this case I need to know the AD object Id of the group I'm querying for. In my case it was always the same group so I stored the group Id in the mobile service's configuration. My class looked like this:

[AuthorizeLevel(AuthorizationLevel.User)]
[RoutePrefix("api/WorkerList")]
public class WorkerListController : ApiController
{
    // GET api/WorkerList
    public async Task<IList<UserProfile>> Get()
    {
        var authenticationResult = await AdIntegration.GetAuthenticationTokenAsync();
        var graphConnection = new GraphConnection(authenticationResult.AccessToken);
        var adWorkers = graphConnection.Get<Group>(CloudConfigurationManager.GetSetting(Constants.ConfigurationKeys.WorkerAdGroup));
        var members = graphConnection.GetLinkedObjects(adWorkers, LinkProperty.Members, null, -1);

        var returnValue = new List<UserProfile>();
        foreach (var member in members.Results)
        {
            var user = member as User;
            if (user != null && (!user.AccountEnabled.HasValue || user.AccountEnabled.Value))
            {
                var groups = graphConnection.GetLinkedObjects(user, LinkProperty.MemberOf, null, -1);
                returnValue.Add(new UserProfile
                    {
                        FullName = user.DisplayName,
                        FirstName = user.GivenName,
                        LastName = user.Surname,
                        UserId = user.UserPrincipalName,
                        Manager = groups.Results.ToList().Any(g => g.ObjectId == CloudConfigurationManager.GetSetting(Constants.ConfigurationKeys.ManagerAdGroup))
                    });                    
            }
        }
        return returnValue;
    }
}

I started the same way as last time, I ensured there was a user logged in to access the Get method and then got the authentication token for the currently logged on user that I can use to connect to Azure AD. Like last time I query AD but instead of querying the User objects with a user's object Id, I query the groups with the group Id stored in the service's configuration. Calling GetLinkedObjects on a group gets me the members of a group, which is exactly what I want. With this it is simply a matter of enumerating through the group members and sending the results back to the caller.

To use this I make a similar call from the client as last time:

var workers = await service.InvokeApiAsync<IList<UserProfile>>("WorkerList", HttpMethod.Get, null);

That's it. Making simple queries against Azure AD is possible as long as you use the correct version of the libraries and know how to call into the old API.

All code and a working Azure Mobile Service .Net project can be found in the development branch under the Azure folder of the following repository:

https://github.com/Magenic/WhitepaperPerformance/tree/develop

No comments:

Post a Comment