Windows Azure Active Directory: Querying the Graph API for group memberships

This is the third part in my series of posts on Windows Azure AD. In this post I go through the steps required to get your application talking to the AD Graph API, and the things that stumped me along the way. The reason we’re interested in the Graph API to begin with is that WAAD Authentication provides us with the user’s identity, but doesn’t tell us anything else about them. If we want more details, such as group memberships, we need to do some extra work.

Dependencies

We begin this part by adding a new Service Reference to the project. For the address of the service, type http://graph.windows.net/offbeatdemo.onmicrosoft.com (replacing offbeatdemo.onmicrosoft.com with your own tenant domain name) and click “Go”. After a moment of waiting, Visual Studio should generate the necessary metadata files. Here you can give your service a namespace – remember, it’s relative to your project’s default namespace, so let’s call it “ActiveDirectoryGraph”.

vs-graph-reference

Once you hit OK, the service reference is generated as per usual. This is where the fun begins. Go to the References node in your project, right-click and select “Manage NuGet Packages…”. Select “Online” from the left, click on the search box and type in “Microsoft.WindowsAzure.ActiveDirectory.Authentication” and you should see a single matching package.

vs-nuget-aal

Click on “Install” to have NuGet fetch the library for you. The library in question is the Windows Azure Authentication Library, or AAL for short. It has all kinds of features, but the reason we need it is to authenticate our application so it can make Graph API requests. Somewhat counterintuitively, AAL depends on System.Windows.Forms of all things, so before your code will compile, you will need to add that as a reference to your project.

Using the API

Create a new class in your project – you can call it what you like, but I’m calling mine “GraphClient”. Paste in the following code – you may have to change some namespaces, but if you’ve added the service reference and the AAL package correctly, it should compile as is:


I’m not going to go through the implementation details of the class now – if you’ve got questions or feedback, leave a comment here or in the gist at GitHub. In order to use this class, what you need to do is find out some details about your tenant domain and Service Principal. It’s not entirely obvious why, but the only place where I’ve found the tenant id so far is the FederationMetadata.xml file (as documented in the article Integrating Multi-Tenant Cloud Applications with Windows Azure Active Directory) – note that the file I linked to is the federation metadata for my demo tenant domain, so you need to change the URL to get your own metadata. Locate the entityId attribute on the EntityDescriptor tag.

federation-metadata-tenantid

The entityId attribute looks like this: spn:00000001-0000-0000-c000-000000000000@a071bf68-ee1d-46aa-ac6d-cfddf3826050 – the emphasized part after the @ sign is the tenant id. Yours will be different from mine of course – copy the id somewhere handy, because you will need it soon. You’re also going to need the app principal id of the service principal you created in the previous installment. Finally, you’ll need to grant your application the right to read the directory, and create a symmetric key known to both WAAD and your application – it’s a key you will use to sign your requests, so WAAD will know it’s you making them. Here’s how you do all that: open the MSOL module for PowerShell and use Connect-MsolService to connect to your WAAD tenant. Run Import-Module MSOnlineExtended –Force to make all the commandlets available, and then run Get-MsolServicePrincipal | select DisplayName,AppPrincipalId to list your service principals.

PS C:\Windows\system32> Get-MsolServicePrincipal | select DisplayName,AppPrincipalId

DisplayName                             AppPrincipalId
-----------                             --------------
Microsoft.Azure.ActiveDirectory         00000002-0000-0000-c000-000000000000
Microsoft.Azure.ActiveDirectoryUX       0000000c-0000-0000-c000-000000000000
WAADDemo App                            e8a3050f-0c61-46bd-9808-ff7dd5dcdb4b

The resulting list will show you the app principal id (in this case, e8a3050f-0c61-46bd-9808-ff7dd5dcdb4b) – copy it somewhere handy. Next, we use that to create a new symmetric key with New-MsolServicePrincipalCredential:

PS C:\Windows\system32> New-MsolServicePrincipalCredential -AppPrincipalId e8a3050f-0c61-46bd-9808-ff7dd5dcdb4b
The following symmetric key was created as one was not supplied JH0QbohY5/+IW25zzukjuwPjr6mpnMhgicgVA4SfF9A=

Save the symmetric key somewhere too. As far as I know, there is no way to restore the value if you lose it – you’ll have to create a new key instead. You’ve got everything your application needs, but there’s still one more thing left to do: granting privileges.

PS C:\Windows\system32> Add-MsolRoleMember -RoleName "Service Support Administrator" `
 -RoleMemberType ServicePrincipal -RoleMemberObjectId 3dc125e6-d518-40d2-9392-87a03dac8f68

The “Service Support Administrator” role grants read-only access to the directory. The RoleMemberType switch is needed to inform WAAD that we’re granting the privilege to an application instead of a user or a group, and the RoleMemberObjectId switch identifies the Service Principal. Note that the Object Id is different from the AppPrincipalId we used earlier – if you don’t have the Object Id written down anywhere, you can use Get-MsolServicePrincipal | select DisplayName,ObjectId to get it. Now, you can use the collected values to create an instance of GraphClient and use it. Add this to a controller action:

var client = new GraphClient("offbeatdemo.onmicrosoft.com""a071bf68-ee1d-46aa-ac6d-cfddf3826050",
 "e8a3050f-0c61-46bd-9808-ff7dd5dcdb4b""JH0QbohY5/+IW25zzukjuwPjr6mpnMhgicgVA4SfF9A=");
 
var groups = client.GetUserGroups(User.Identity.Name);

return View(groups);

Now you should be able to display your user’s group memberships in the view:

waad-app-with-groups

Wrapping up

None of this was particularly difficult to do from a programming perspective – the trick is finding all the information you need! It doesn’t much help that most of the examples out there deal with either SaaS applications that are registered in the Seller Dashboard or applications that use ACS instead of WAAD. The steps are largely the same, but the places where you look for IDs and keys are wildly different. Merely listing the user’s group memberships isn’t very interesting. Now that we’ve got them, we could use them for authorization within the application – which is what we’re doing in the next part of this series.

26 comments

  • Good post!

    “Somewhat counterintuitively, AAL depends on System.Windows.Forms of all things, so before your code will compile, you will need to add that as a reference to your project.”

    That’s a nice daily WTF. 😀

  • Actually, there is a somewhat valid reason for that. AAL also contains the things you need to enable federated authentication for your GUI application — a chromeless, embedded IE window. That’d be the reason. But it still kinda feels wrong to reference S.W.F from a web app… :-)

  • I’m probably doing something stupid!

    The GetUserGroups method is failing. The problem is on this line in GetAuthorizationHeader:

    assertionCredential = authContext.AcquireToken(GetServiceRealm(), credential);

    The error is:AAL 0x80100018: Token request from Access Control service failed. Check InnerException for more details

    ACS50000: There was an error issuing a token. ACS50001: Relying party with name ‘00000002-0000-0000-c000-000000000000/graph.windows.net@XXmydomainhereXX.onmicrosoft.com’ was not found. Trace ID: a3fc144b-53a3-4573-b3fd-a78bae0da20dTimestamp: 2012-12-19 08:22:41Z

    So, the code is failing to get the token that it needs to call the Graph stuff. What I can’t work out is why the token request is failing. I’m probably doing something stupid, or perhaps the Graph interface has changed subtly since the blog was written.

    Any pointers would be welcome.

    Cheers,Nick

  • Hi, Nick.

    The code I wrote alongside with this blog post still seems to work with my setup, so it seems more likely to me that you’ve encountered yet another way this can go wrong.

    From the error message, the problem appears to be the relying party name. Now, it looks correct to me, so just to be on the safe side, let’s see if I’ve made an incorrect assumption in my post.

    Fire up the MSOL PowerShell, run Import-Module MSOnlineExtended -Force and then try:Get-MsolServicePrincipal | Select DisplayName,AppPrincipalIdOne of the returned entries should be named Microsoft.Azure.ActiveDirectory. Verify whether or not it has the same AppPrincipalId I’ve hard-coded in my example (00000002-0000-0000-c000-000000000000). If it doesn’t, try changing the constant to whatever the ID is in your case.

    If it does, we’ll have to think of something else.

  • On further reflection, every sample from Microsoft seems to assume that GUID is a constant, so that’s probably not the reason.

    I’m assuming that when you use the GraphClient class, you’ve replaced all four constructor parameters with corresponding values from your setup?

  • Lauri, Thanks for the prompt response. I have checked that the GUID is as expected, so it’s not that.

    The four constructor parameters are:

    Domain – as expected.
    TenantId – checked that is the same as returned as TenantId within the list of claims returned by the SSO operation.
    AppPrincipalId – checked that it is the Id of my application as returned by Get-MsolServicePrincipal.
    Symmetric Key – I have one from when the Service Principal was created (which I am using successfully for SSO), and another from when I created the Credential. I have tried both here with the same result.

    To try and nail it down, I have tried an incorrect domain – that results in a different error message: There was an error deserializing the object of type Microsoft.WindowsAzure.ActiveDirectory.Authentication.

    ErrorResponse. Encountered unexpected character ‘T’.

    I have also tried an incorrect Tenant Id – Interestingly, that gives the original “relying party not found error”.

    Trying an invalid AppPrincipalId gives ACS50027: JWT token is invalid.

    Not sure if any of that helps!

    I did read somewhere (can’t find it now) that there is a strange issue with [older] Active Directories created via the Office 365 route.Still clueless here. :-(

    Thanks,Nick

  • Just for kicks, try to replace the AzureAppPrincipalId constant with the value "AzureActiveDirectory" instead of the GUID. It looks like the SPN for AD in O365 might be different from a pure WAAD tenant.

  • Thanks Lauri. Tried that. Still an error, but a different one this time. In this code block:

    var user = dataService.Users
    .Where(u => u.AccountEnabled == true && u.UserPrincipalName == userPrincipalName)
    .AsEnumerable()
    .SingleOrDefault();

    I am getting a new error:

    An error occurred while processing this request.

    And then an inner exception saying:Authentication_Unauthorized

    Maybe that’s suggesting that something else in my configuration isn’t right?

    An interesting update on another post about O365 WAAD http://azurecoder.azurewebsites.net/?p=772#comments

    Argh!

    Cheers, Nick.

  • The plot thickens, and I may have a solution for you. Try this:

    Revert the AzureAdPrincipalId back to the GUID value, and let’s approach this from from the other direction.

    Look at this gist:

    https://gist.github.com/4338524

    That updates the list of SPNs for Azure AD with values that are otherwise identical to the ones already there, but with the GUID AppPrincipalId instead of AzureActiveDirectory. That seemed to do the trick for me. :)

  • Of course, the constant was AzureAppPrincipalId, not AzureAdPrincipalId…Oh, and in case it wasn’t obvious before, I was able to reproduce your issue with our O365 subscription and make it work with the stuff I put in the gist. :-)

  • ABSOLUTELY BRILLIANT – that is working now. It seems that we have been charting new ground for the Office 365 flavour of WAAD.

    Thank you SO MUCH for finding the time and inclination to help me out!!

    Best wishes and kind regards,Nick

  • Hi Lauri,

    thanks for your series on doing GroupBasedAuth with WAAD!

    I do have a question though, when adding the rolemember I receive the following error:

    Add-MsolRoleMember : Cannot bind parameter ‘RoleMemberType’. Cannot convert val
    ue “ServicePrincipal” to type “Microsoft.Online.Administration.RoleMemberType”.
    Error: “Unable to match the identifier name ServicePrincipal to a valid enumer
    ator name. Specify one of the following enumerator names and try again: User,
    Group”

    Did the API change, or am I doing something wrong here? I’m not using a O365 WAAD, it’s a recently setup fresh WAAD.

    Thanks!

    Erik

  • Hi Erik,

    I’m pretty sure the problem is your version of the MSOL PowerShell module — try downloading the version behind this link:

    http://onlinehelp.microsoft.com/Office365-enterprises/ff652560.aspx

    I mentioned this in the first part of my series:

    http://blog.rytmis.net/2012/12/windows-azure-active-directory.html

    The older versions don’t have that enum value defined.

  • To be more specific, the file version of C:\Windows\System32\WindowsPowerShell\v1.0\Modules\MSOnlineExtended\Microsoft.Online.Administration.Automation.PSModule.dll should be 1.0.5288.0 (or newer, but that’s the version I use).

  • Hi Lauri,

    very stupid mistake, I got the link to the PSExtensions via a nice blog of Vittorio Bertocci to glue WAAD to ACS (http://blogs.msdn.com/b/vbertocci/archive/2012/11/07/provisioning-a-directory-tenant-as-an-identity-provider-in-an-acs-namespace.aspx), but indeed it’s the old version!

    Thank you very much for your prompt responses, everything is working perfect!

    Erik

  • Hi Lauri,

    I get this error
    AAL 0x80100018: Token request from Access Control service failed. Check InnerException for more details

    {“ACS50027: JWT token is invalid. ACS50027: Invalid JWT token. The token is not yet valid.\nValidFrom: ’02/07/2013 08:17:00’\nCurrent time: ’02/07/2013 08:11:12′ \r\nTrace ID: a59f74b8-5e27-4253-aa39-d9f27bd96801\r\nTimestamp: 2013-02-07 08:11:12Z”}
    but when I tried the same application in different windows8 laptop that works fine. but I get the same error in my other windows8 laptop.

    please let me know if i need to install anything?
    I just installed vs2012, AAL package . anything else do i need to install?

    kind regards,
    Snegha

  • Snegha,

    From the error message, it looks like you’re hitting some sort of time synchronization issue. Since it works on one computer and not the other, can you verify that the problem machine has its clock set to the correct time?

  • Hi, thanks for the set of articles. I am having an odd issue and not entirely sure why. Hopefully something simple. I have copied in the class you outline and updated the namespaces. All is building apart from it is stating that the ‘admin2.ActiveDirectoryGraph.Microsoft.WindowsAzure.ActiveDirectory.DirectoryDataService’ does not contain a definition for ‘Groups’ and the same for users, relating to the GetUserGroups method.

    Any help much appreciated, I can’t see what I have done wrong.

    Steve.

  • Lauri Kotilainen

    Hi, Steve!

    The Graph API has been updated, and since Visual Studio’s Service Reference tool doesn’t provide a way to set the API version header, you’re getting a newer contract. I haven’t familiarized myself with the API changes yet, so I’m not sure what to tell you. I’ll try to look into it Real Soon Now [tm].

  • Lauri Kotilainen

    Hello again, Steve & co.

    From reading examples and looking at the generated proxy code, I’m led to believe that instead of accessing dataService.Users, after the API update, the correct way is to access

    dataService.directoryObjects.OfType() as DataServiceQuery

    instead.

    You might want to check out the Graph API helper library at http://code.msdn.microsoft.com/Windows-Azure-AD-Graph-API-a8c72e18 :)

  • Thanks Lauri, if you could work that into an updated post that would be fantastic. I have tried to update your code to make it work but I’m afraid I fall short of your skills. I can get it to complile but get runtime error after error.

  • Lauri, should have mentioned also. I did look at the updated sample they have done, it works well but I struggled when attempting to move the code to a helper class or action filter rather than hanging off the controller where my actions are. I am showing my inexperience again, but because it using httpcontext I couldn’t work out how to move the code away from the controller.

  • Lauri Kotilainen

    Can you point me to the specific example you’re referring to?

  • Sure, http://code.msdn.microsoft.com/Write-Sample-App-for-79e55502#content. Each controller has an entry at the top using HTTP context and then actions have the code to do the work. I couldn’t work out how to put the code into helpers or filters. I tried doing something with HTTPCONTEXT and also tried to pass the directoryservice into the helper but couldn’t get it to work.

  • Lauri Kotilainen

    All right, so now that I’ve looked at the sample, I understand the problem. :)

    First, whenever you know there’s an incoming HTTP request going on but you don’t have a handle on the context, you can access the context via HttpContext.Current, and use that helper as follows:

    MVCGraphServiceHelper.CreateDirectoryDataService(new HttpContextWrapper(HttpContext.Current).Session);

    The HttpContext needs to be wrapped for reasons I won’t go into now. That’s a bit ugly, and while the ugliness is fixable, I’m opting to keep this simple.

    Now, if I understood you correctly and you’re writing an Action Filter, then you don’t need to go through HttpContext.Current, since you already have a handle on the HttpContext. Like this:

    public class YourFilterAttribute : ActionFilterAttribute {
    public override void OnActionExecuting(ActionExecutingContext filterContext) {
    base.OnActionExecuting(filterContext);
    var service = MVCGraphServiceHelper.CreateDirectoryDataService(filterContext.HttpContext.Session);
    // Do your thing here
    }
    }

    I hope this helps you — let me know if you need further assistance. I’ll try to update the series to reflect the latest changes at some point.

  • I can’t thank you enough Lauri, it works a treat. I could have never worked that out on my own!!! Steve.

Leave a Reply

Your email address will not be published. Required fields are marked *