Walk through for building a .Net application for accessing Windows Azure Active Directory Graph Service
The post provides a walk through for accessing Azure Active Directory (AAD) Graph Service through a .Net application. If you would like to download a full working sample, you can download the sample MVC application from here. If you would like to learn more about Azure Active Directory Graph Service, visit our previous blog posts.
Creating a sample tenant
You can now manage your azure active directory tenants through Windows Azure Management Portal. If you do not have an azure subscription, it is easy to sign up for one using free trial. If you login to the Azure management portal, you will see an active directory tab. If you have an existing Azure AD tenant associated with this Azure subscription, it will show up here. If you don't have one, you can create one using the UX.
Register your application for working with AAD
The next step is to register our application with Azure AD which will let the application access the data in Azure AD through Graph. The following MSDN topic provides a step by step instruction on registering application: https://msdn.microsoft.com/library/windowsazure/dn151791.aspx#BKMK_Configuring . If you followed the steps, you will have the ClientID and Key values that you will need later as you build the application.
Getting Authentication Token from Windows Azure AD for accessing Graph Service
An authentication token from Windows Azure AD is required to access Graph Service. The authentication token proves that the application has been authorized to access the directory information for the tenant in Azure AD through Graph service. To get the token, we need to provide the TenantDomainName, AppPrincipalId/ClientId and Password/Key generated in the previous step to Azure AD via a HTTP post request. Here is a code snippet that puts together all of this information in the format that Azure AD expects and sends it over in a HTTP Post request. Windows Azure AD can provide authentication tokens for accessing different services and if you need to change the code to get token for accessing a different service than Graph, you have to provide the principal id of that service instead of the one for Graph service.
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(String.Format(
"https://login.windows.net/{0}/oauth2/token?api-version=1.0",
tenantName));
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
string postData = "grant_type=client_credentials";
postData += "&resource=" + HttpUtility.UrlEncode("https://graph.windows.net");
postData += "&client_id=" + HttpUtility.UrlEncode(appPrincipalId);
postData += "&client_secret=" + HttpUtility.UrlEncode(password);
byte[] data = encoding.GetBytes(postData);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;
using (Stream stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
If successfully authenticated, we will get back a JSON response from Azure AD with token_type, access_token and a few other fields. A sample response for a successful authentication request will look like this:
{
"token_type":"Bearer",
"access_token":"eyJ0eXA…..Q",
"expires_in":"43199",
"not_before":"1358887748",
"expires_on":"1358930948",
"resource":00000002-0000-0000-c000-000000000000/graph.windows.net@4fd2b2f2-ea27-4fe5-a8f3-7b1a7c975f34
}
We need to take the token_type and access_token fields from this response for putting together the Authorization header for Graph service.
using (var response = request.GetResponse())
{
using (var stream = response.GetResponseStream())
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(AADTokenFormat));
AADTokenFormat token = (AADTokenFormat)(ser.ReadObject(stream));
returnString.Format(CultureInfo.InvariantCulture,"{0}{1}{2}",
token.token_type, " ", token.access_token);
}
}
The AADTokenFormat class used above has two fields token_type and access_token and is used to read the JSON response into the strongly typed object.
[DataContract]
internal class AADTokenFormat
{
[DataMember]
internal string token_type { get; set; }
[DataMember]
internal string access_token { get; set; }
}
Creating the Service Reference for Graph Service
Now that we have the Authorization header, we are ready to access Graph service for querying and updating the information for our tenant. In the last post we saw that all operations against Graph service can be done using standard HTTP operations like GET, POST, DELETE etc. and advanced query operations can be done using OData query options via HTTP request parameters. We can definitely work with the Service using HTTPRequest and HTTPResponse by putting together the requests manually and parse the responses and extract information we need. But since Graph service is an OData service, we can use the WCF Data Services client library which provides a strongly typed access for working with OData services.
The first step for using WCF Data Services client library is to create a Service Reference. You can add a Service Reference in Visual Studio by right clicking the project and clicking “Add Service Reference”. In the Address text box, type the following URL which points to the metadata of the Graph service: https://graph.windows.net/contoso.onmicrosoft.com/$metadata ( you can replace contoso with your tenant name). The Service Reference thus generated will have a set of classes that will have 1:1 correspondence with the collections/feeds exposed by the Graph service. If you double click the Service Reference, you can browse through the classes and you will see classes for User, Group, Contact etc. Another class that’s part of the Service Reference will be a class called DirectoryDataService which derives from DataServiceContext class. The DirectoryDataService class will expose collections of types exposed by Graph service like DirectoryObjects, Applications etc. as IQueryable. You can query over these collections using LINQ queries like any other IQueryable collections. The DirectoryDataService class also has the ability to track the changes of objects and exposes a SaveChanges method that can be used to submit the appropriate POST, PATCH or DELETE requests to the Graph service.
Adding the required HTTP request headers
Each HTTP request to the Graph service needs a couple of headers - Authorization Header and the Version of the Graph service. The easiest way to attach these headers on each request is by hooking up to an event on DirectoryDataService instance as shown in the below code.
Code to hook up the event handler:
var svcUri = new Uri("https://graph.windows.net/" +
ConfigurationManager.AppSettings["TenantDomainName"]);
var graphService = new DirectoryDataService(svcUri);
// Register the event handler that adds the headers for HTTP requests
graphService.SendingRequest += new EventHandler<SendingRequestEventArgs>(
OnSendingRequest);
return graphService;
Code in the event handler that adds the headers:
// Add an Authorization header that contains an OAuth WRAP access token to the request.
string authzHeader = AzureActiveDirectoryGraphAuthorizationHelper.
GetAuthorizationToken(ConfigurationManager.AppSettings["TenantDomainName"],
ConfigurationManager.AppSettings["AppPrincipalId"],
ConfigurationManager.AppSettings["Password"]);
e.RequestHeaders.Add("Authorization", authzHeader);
Querying for all objects in a collection
Below is a snippet of code that gets all the Users from the Graph service. As you can see, it is as simple as writing these two lines of code to get back a collection from the Service. The reason we use a Helper method to create the instance of the DirectoryDataService is to hook up the event handler that adds the required Headers as we have seen in the previous section.
DirectoryDataService graphService = DirectoryDataServiceHelper.CreateDirectoryDataService();
List<User> users = graphService.directoryObjects.OfType<User>().ToList();
So what really happened?
So what magic happened under the covers to make the things so simple? The Data Services client library produced a HTTP request under the covers based on our LINQ query (graphService.Users), read the response from the Graph service, deserialized the response stream and handed us back the strongly typed objects. Here’s a sample of the HTTP request that is sent out by the WCF client library and response that it gets back from the Graph service. Our application doesn’t need to deal with any of this but it is still good to understand what happens under the covers. Our last blog post has plenty of examples (including this one) for the HTTP request and response formats for various operations on Graph service.
Request URL:
HTTP Method: GET
Response:
{
"odata.metadata": "https://graph.windows.net/GraphDir1.OnMicrosoft.com/$metadata#directoryObjects/Microsoft.WindowsAzure.ActiveDirectory.User"
"value":[
{
"objectId": "e428a9cb-7550-4991-afc3-48fe8b60be33",
"accountEnabled": true,
"city": "Seattle",
"displayName": "Adam Barr"
},
{
"objectId": "bafb9fea-d7f3-4cec-af6a-bca2b553e83b",
"accountEnabled": true,
"city": null,
"displayName": "Admin",
}
] } |
Querying Users collection for a Key value
All Collection types exposed by Graph service have the objectId property which acts as a Key property. We can do LINQ query on the objectId property to retrieve a User entry with the given Key value. You can also see the URL that the query gets mapped to below.
LINQ Query:
User user = graphService.users.Where
(it => (it.objectId == 'e428a9cb-7550-4991-afc3-48fe8b60be33'))
.SingleOrDefault();
Request URL:
https://graph.windows.net/GraphDir1.OnMicrosoft.com/users/e428a9cb-7550-4991-afc3-48fe8b60be33' |
Querying Users collection for a non-Key property value
The query for a non-Key property looks very similar to the one you did for a Key property. Since it is not a Key property, you cannot expect the query to return at most one object. But the URL that the query gets mapped to under the covers is very different. While the query on a Key property gets expressed in the URL itself, all other queries are expressed via Request parameters as defined by OData specification. You can use LINQ queries for different operations supported by the AD Graph service and the WCF Data Services client library will translate them to the correct HTTP request. Currently Graph service supports “Where”, “Top” and “SkipToken” operators. We plan to support more operators like “Select”, “Expand” etc. in the near future. Here we see an example for querying Users collection for a particular displayName.
LINQ Query:
IEnumerable<User> users = graphService.directoryObjects.OfType<User>().
Where(user => user.displayName.Equals(‘Derek Brown’));
Request URL:
https://graph.windows.net/GraphDir1.OnMicrosoft.com/users?$filter=displayName eq 'Derek Brown' |
Navigating through Links
Objects exposed by the Graph service are interconnected via various links. For example the following concepts are represented by links in the Graph service, Users belong to different Roles and Groups, Groups have Members which are Users or Contacts, Roles have Members which are Users or Contacts etc. All of these links are exposed as first class properties on the generated types in the Service Reference we created. These properties are referred to as Navigation properties. These navigation properties are not fetched by default though. For example, if you fetch the User, the “memberOf” property on the User will be null. And this is to be expected since if we load all Navigation properties by default, we could end up navigating through the whole Directory. In order to fetch the Navigation property, we would need to call the LoadProperty on the DirectoryDataService. The code below fetches the “memberOf” property on the User and you can also see the URL that this call maps to under the covers.
Code to fetch the Roles that a User belongs to:
graphService.LoadProperty(user,"memberOf");
var currentRoles = user.memberOf.OfType<Role>();
Request URL:
Create a new entry
You can use the SaveChanges method on DirectoryDataService class to persist any change to the Graph service. Let’s first look at an example for creating a new User object. The SaveChanges call takes the content of newly created User and submits it as the body of a HTTP Post request to the Users collection URL.
User user = new User();
user.displayName = ""Bob";
user.userPrincipalName = "alias@tenant.onmicrosoft.com";
user.mailNickname = ""alias";
user.password = ""xxxxxxxx";
user.accountEnabled = true;
graphService.AddToDirectoryObjects(user);
graphService.SaveChanges();
Updating an entry
Once you fetch an object through LINQ query, any changes you do on the object are tracked by the DirectoryDataService instance that was used to fetch the object. In such a case, you can just do SaveChanges and the DirectoryDataService will produce the appropriate HTTP Patch/Merge request for submitting the change. But in case you are building a Web application, you typically would not cache the instance of DirectoryDataService instance between the requests. In such a case, you can make changes to the object and explicitly tell the DirectoryDataService instance that the Object has changed and then call SaveChanges as shown in the code below.
User user = graphService.directoryObjects.OfType<User>().
Where(it => (it.objectId == user.objectId)).SingleOrDefault();
user.displayName = newDisplayName;
user.mailNickname = newAlias;
graphService.UpdateObject(user);
graphService.SaveChanges(SaveChangesOptions.PatchOnUpdate);
Deleting an entry
To delete an entry, you can call DeleteObject method on the DirectoryDataService and then call SaveChanges. The call will translate to a HTTP Delete request submitted to the Graph Service.
User user = graphService.directoryObjects.OfType<User>().
Where(it => (it.objectId == user.objectId)).SingleOrDefault();
graphService.DeleteObject(user);
graphService.SaveChanges();
Adding or Removing Links between existing Entries
You can add or remove a link between two existing entries via AddLink and RemoveLink methods on DirectoryDataService class respectively followed by a call to SaveChanges. The changes will be mapped to a HTTP POST for AddLink and HTTP DELETE request for RemoveLink. Below is a code snippet for adding and removing “Members” link between a User and Role entry. In this case both user and role exists in the Graph already.
graphService.AddLink(existingRole1, "members", existingUser1);
graphService.DeleteLink(existingRole1,"members", existingUser2);
graphService.SaveChanges();
Using the code snippets provided in this post, you should able to access and manipulate most of the information in AAD Graph service from your .Net application. If you have any questions or would like to see some particular code snippet, please leave a comment and we will be happy to add it.
Comments
- Anonymous
July 22, 2013
The comment has been removed - Anonymous
July 23, 2013
The comment has been removed - Anonymous
May 26, 2014
Is there a way to find the users that is logged? The claim doesn't contain the upn... Thanks - Anonymous
March 20, 2015
Please note, the page mentioned in "Register your application for working with AAD" for registering an application has moved to the following URL: msdn.microsoft.com/.../b08d91fa-6a64-4deb-92f4-f5857add9ed8 - Anonymous
July 10, 2015
The comment has been removed - Anonymous
August 30, 2015
Hi, I want to get all users and their groups in one URL call . Right now i am using url- "graph.windows.net/.../users$expand=memberOf&api-version=1.0" It shows all users and their groups. but groups displayed are<20. How can I display all groups of each user with minimum time?