Azure Active Directory Graph API Wrapper to help make it a bit easier!

I have recently been trying to program against the Azure Active Directory (AAD) using the Microsoft.Azure.ActiveDirectory.GraphClient library. Unfortunately this library literally has no useful comments to assist understanding  or clarify parameters etc.. Let alone how best to use or implement objects and methods or what and why exception may occur.

Equally the MSDN documentation seems to be lacking in any examples and really has minimal comments (although I see it’s getting a bit better…I think).

To this end I have created a ‘wrapper / handler’ to simplify all sorts of AAD interactions called AADGraphHandler. It effectively will help manage the creation of the ActiveDirectoryClient and a bunch of it’s operations. You can find this on GitHub at https://github.com/nrogoff/AADGraphHandler

You can get access to the ActiveDirectoryClient directly, and so any methods not covered yet. (e.g. Adding and removing roles to a user. Just not needed it yet!)

You can insert this into your projects using NuGet by searching for AADGraphHandler or at https://www.nuget.org/packages/hms.Common.Azure.AADGraphHandler/ 

The open source code can be found on GitHub . All contributors are welcome.

Two Authentication Modes for Active Directory Client

There are two modes to interacting with AAD. As an Application or as a User. There are some permissions that an Application can never have. One of these is the ability to delete a User account. In order to delete a user account the request needs to be in the context of a user with a ‘User Account Administrator’ role.

see https://docs.microsoft.com/en-gb/azure/active-directory/active-directory-assign-admin-roles for more details of AAD administrator roles.

Two AAD’s

In Azure there is ‘Active Directory‘ and then there’s ‘Active Directory B2C‘. User accounts reside in parent ‘Active Directory’ tenant for both, but you can register Applications at both the ‘Active Directory’ and the ‘B2C’ extension. Depending on what actions you are taking depends on which Application Account you use. This can be confusing to say the least! The Graph API really deals with the Azure Active Directory and not the B2C extensions, so you’ll be need to create your application account there.

Two Types of AD Application Accounts

  • WebApp / API
    • Has direct and delegated permissions
  • Native
    • Only has direct user permissions

Most tests use the ‘WebApp/API’ Application account, but in order to use the AAD Graph Handler with the User Context, you will need to create a ‘Native’ Application account.

How To Use the AADGraphHandler

The best way to see how to use the AADGraphHandler is to examine the test project hms.common.azure.aadTests here https://github.com/nrogoff/AADGraphHandler/tree/master/src/hms.Common.Azure.AadTests (this is an integration test project and has interactive logins).

Application Settings

The Azure Active Directory Client requires a number of configuration settings. Below is the full list that you will need to add to your web.config or app.config or other startup config.

<!-- START Azure AD For Integration Tests -->
<!-- REPLACE VALUES BELOW WITH YOUR DOMAIN VALUES -->
<add key="aad:TenantId" value="12345678-1234-1234-1234-012345678912" />
<add key="aad:TenantDisplayName" value="yourdomain.com" />
<add key="aad:TenantDefaultDomain" value="yourdomain.com" />
<add key="aad:TenantInitialDomainName" value="yourtenantname.onmicrosoft.com" />
<add key="aad:ApiAppClientId" value="12345678-1234-1234-1234-012345678912" />
<add key="aad:ApiAppClientSecret" value="1234567891234567891234567981234567891234567=" />
<add key="aad:NativeAppClientId" value="12345678-1234-1234-1234-012345678912" />
<add key="aad:GraphServiceRootUri" value="https://graph.windows.net" />
<add key="aad:AuthorityServiceRootUri" value="https://login.microsoftonline.com/" />
<add key="aad:RedirectUri" value="https://localhost" />
<add key="aad:IntegrationUserAdminUserName" value="IntegrationTest.Admin@yourdomain.com" />
<!-- END Azure AD -->

You will need to reference these when instantiating the AADGraphHandler. You will either want to do this in your IoC container boostrap or something simple like the example below (this is how the test does it!).

#region Test Parameters - Configure for your AD
 
//NOTE: Change the following settings in the app.config to test against your Azure AD or B2C
private static readonly string TenantId = ConfigurationManager.AppSettings["aad:TenantId"];
private static readonly string TenantDisplayName = ConfigurationManager.AppSettings["aad:TenantDisplayName"];
private static readonly string TenantDefaultDomain = ConfigurationManager.AppSettings["aad:TenantDefaultDomain"];
private static readonly string TenantInitialDomainName = ConfigurationManager.AppSettings["aad:TenantInitialDomainName"];
 
private static readonly string ApiAppClientId = ConfigurationManager.AppSettings["aad:ApiAppClientId"];
private static readonly string ApiAppClientSecret = ConfigurationManager.AppSettings["aad:ApiAppClientSecret"]; 
 
private static readonly string NativeAppClientId = ConfigurationManager.AppSettings["aad:NativeAppClientId"]; //used for as user tests
 
private static readonly string GraphServiceRootUri = ConfigurationManager.AppSettings["aad:GraphServiceRootUri"];
private static readonly string AuthorityServiceRootUri = ConfigurationManager.AppSettings["aad:AuthorityServiceRootUri"];
private static readonly string RedirectUri = ConfigurationManager.AppSettings["aad:RedirectUri"];
 
//NOTE: Integration Test Admin Account details. This is required to run the delete user account tests
//NOTE: and will create a user prompt that will need the password
private static readonly string IntegrationUserAdminUserName = ConfigurationManager.AppSettings["aad:IntegrationUserAdminUserName"];

#endregion

The next step is to decide on how you are going to access the AAD Graph. Using the Application context or as a delegated user context or both. Each context has it’s own configuration object.

Use as Application

First create a configuration. This will ideally be setup in your IoC container registration bootstrap process. Each object type has a corresponding Interface.

var appConfig = new AADGraphHandler.AADGraphHandlerConfigurationForApp
{
    TenantId = TenantId,
    GraphServiceRootUri = new Uri(GraphServiceRootUri),
    AuthorityServiceRootUri = new Uri(AuthorityServiceRootUri),
    TenantDisplayName = TenantDisplayName,
    AppClientId = ApiAppClientId,
    AppClientSecret = ApiAppClientSecret
};

Then create the AADGraphHandler passing in the app config

var graphHAndler = new AADGraphHandler(appConfig);

Use as User

Its the same process as for an application, but he config object differs slightly.

var userConfig = new AADGraphHandler.AADGraphHandlerConfigurationForUser
{
    TenantId = TenantId,
    GraphServiceRootUri = new Uri(GraphServiceRootUri),
    AuthorityServiceRootUri = new Uri(AuthorityServiceRootUri),
    TenantDisplayName = TenantDisplayName,
    RedirectUri = new Uri(RedirectUri),
    AppClientId = NativeAppClientId,
    Username = username
};

Then create the AADGraphHandler passing in the user config

var graphHAndler = new AADGraphHandler(userConfig);

Add Caching

The AADGraphHandler will take advantage of a caching provider if you are using one. You simply need to create a provider object that conforms to the ICacheProvider interface and then add this to the CacheProvider property in the App or User config object you created above.

Currently only the following methods will perform CRUD operations on the cache. This was initially designed for good performance when using IsInGroup() for MVC Controller [Authorize("GroupName")] attributes. I’ll write another blog about that shortly.

  • GetUserAsync()
  • GetUser()
  • UpdateUserAsync()
  • UpdateUser()
  • DeleteUserAsync()
  • DeleteUser()
  • AddUserToGroupAsync()
  • AddUserToGroup()
  • RemoveUserFromGroupAsync()
  • RemoveUserFromGroup()
  • IsInGroupAsync()
  • IsInGroup()
  • HasRoleAsync()
  • HasRole()

The cache keys are as follows:

  • HMSAAD:User:{AAD ObjectId}
  • HMSAAD:User:{AAD ObjectId}:IsInGroup:{GroupName}
  • HMSAAD:User:{AAD ObjectId}:HasRole:{RoleName}

You can change the default ‘HMSAAD’ prefix, by setting the AADGraphHandler.CachePrefix property.

Using the AADGraphHandler Operations

Once you have instantiated AADGraphHandler it is simple to execute operations. The best place to see code samples is to look at the AADGraphHandlerTests.cs on GitHub at https://github.com/nrogoff/AADGraphHandler/blob/master/src/hms.Common.Azure.AadTests/AADGraphHandlerTests.cs

Example – Create a new user account
User newuser = new User
{
    GivenName = "John",
    Surname = "Doe",
    DisplayName = "John Doe",
    MailNickname = "John Doe",
    UserPrincipalName = $"JohnDoe.@mydomain.onmicrosoft.com",
    AccountEnabled = true,
    PasswordProfile = new PasswordProfile
                {
                    Password = $"P@ssWord1234",
                    ForceChangePasswordNextLogin = true
                },
    UsageLocation = "GB" 
};
 
bool success = _aadAppGraphHandler.CreateNewUser(newuser);

The AADGraphHandler implements the IDisposable interface, so unless you want to do a lot of reuse it may be best to instantiate in a ‘using’ statement.

Example – Find Users
List<IUser> results = _aadAppGraphHandler.FindUsers(searchString);
Example – IsInGroup and GetSignedInUser
var user = (User)_aadAppGraphHandler.FindUsers($"TestUser@mydomain.onmicrosoft.com").FirstOrDefault();
if(user != null)
{
    bool isInGroup = _aadAppGraphHandler.IsInGroup(user, "Contributors");
}

This method would normally be used with the logged in user rather than looking one up.

var user = _aadAppGraphHandler.GetSignedInUser();
if(user != null)
{
    bool isInGroup = _aadAppGraphHandler.IsInGroup(user, "Contributors");
}

To run the Unit Tests

To run the unit test against your own AD Tenant you will need to set the constants at the top of the AADGraphHandlerTests.cs file.

To do this you will need

  1. An Azure Active Directory Tenant
  2. A Integration Test Admin Users (requires enough permissions to create and delete users)
  3. An Application account of type ‘Native’
  4. An application account of type ‘Web App / API’
  5. Create the following groups or change the group tests(Contributors, Editors, Reviewers)

Once you have these then fill in the various app settings in the app.config file.

Then you should be able to run the unit tests. They are integration tests, so will need human intervention (i.e. to log in) to complete.

You will need to log in with a user that has sufficient permissions to delete the test user accounts created in the earlier tests.

Leave a Reply