read

Update 23.2.2015: Group memberships are now included in the access token and you might want to consider using the new Application Roles functionality instead. Check the new blog post for info.

I’ve been working with Azure Active Directory and claims-based authentication on several occasions lately. I like the model and where it’s all going but the fast development pace of Azure AD combined with the ever-changing ASP.net authentication introduces some new problems from time to time.

One such problem on what I didn’t find good documentation or a blog post on was how to do group-based authorization in Azure AD. Azure AD has had the concept of groups for a while now and especially in an enterprise scenario they’re a common way to restrict access to resources.

In my case I was using the ASP.net MVC 5 & OWIN middleware and the it’s authentication setup is different, albeit more simple than the previous configuration-based approach. So after a while of searching it became apparent that I can still use the ASP.net MVC [Authorize(Role="Admin")] attribute for authorization, I just needed to inject the roles as claims myself. Most of the idea has already been documented at this blog post.

That post, however, doesn’t use OWIN but the old approach and the claims transformation phase is different in OWIN as there’s now ClaimsAuthorizationManager (I think). Or at least I didn’t see configuration for one anywhere so changing it might be problematic. Then I ended up reading Tero’s blog post about claims transformation in OWIN and the picture started to be complete.

So in the end I’ve added a callback on CookieAuthenticationProvider.OnResponseSignIn and read user’s roles from Azure AD at that point. I’m only checking a membership of a single predefined role so I can use the isMemberOf Graph API method. After adding NuGet packages ADAL and Graph API client library and a bit of coding, the final solution looks like this:

public class StartUp 
{
	internal const string AdminRoleName = "Admin";
	private const string ClaimIssuer = "SomeInternalApp1";
	private const string AdminGroupId = "<enter object id of admin group in Azure AD>";
	private const string ClientId = "<enter Azure AD application client ID>";
	private const string ClientKey = "<enter Azure AD application client key>";

	public void ConfigureAuth(IAppBuilder app)
	{
		app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
		app.UseCookieAuthentication(
			 new CookieAuthenticationOptions
			 {
				 AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
				 Provider = new CookieAuthenticationProvider
				 {
					 OnResponseSignIn = ctx =>
					 {
						 ctx.Identity = TransformClaims(ctx.Identity);
					 }
				 }
			 });
		[ .. more configuration as usual .. ] 
	}
 
	private ClaimsIdentity TransformClaims(ClaimsIdentity identity)
	{
		// this check isn't probably necessary if only called from OnResponseSignIn but doesn't hurt either
        if (identity.IsAuthenticated)
	    {
	        var userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
	        if (IsMemberOfAdminGroup(userObjectId))
	            identity.AddClaim(new Claim(System.Security.Claims.ClaimTypes.Role, 
			AdminRoleName, ClaimValueTypes.String, ClaimIssuer));
	    }
	    return identity;
	}		
		
	private bool IsMemberOfAdminGroup(string userObjectId)
	{
		try
		{
			// Get the access token
            var authContext = new AuthenticationContext(_authority);
			var credential = new ClientCredential(ClientId, ClientKey);
			AuthenticationResult result = authContext.AcquireToken("https://graph.windows.net", credential);
		
			var clientRequestId = Guid.NewGuid();
			var graphSettings = new GraphSettings {ApiVersion = "2013-04-05"};
			var graphConnection = new GraphConnection(result.AccessToken, clientRequestId, graphSettings);
			// check group membership
            bool isMemberOf = graphConnection.IsMemberOf(AdminGroupId, userObjectId);
			return isMemberOf;
	    }
	    catch (Exception e)
	    {
	        _logger.ErrorException("An exception occurred while checking admin group membership", e);
	        return false;
	    }
	}
}

Now in the controller it’s possible to restrict access using the same approach as with normal ASP.net roles or on-premise AD groups:

[Authorize(Role=StartUp.AdminRoleName)]
public class TopSecretController : Controller 
{ 
	[...] 
}

This is especially nice from the controller point-of-view as no code changes were needed to read the roles from Azure AD.

Disclaimer: This code is for demonstrating the idea only. It has several points of improvements: standard ADAL in-memory token cache is used, role membership is only checked when logging in, etc. YMMV.

Comments

Blog Logo

Henri Tuomola


Published

Image

Henri Tuomola

Software developer and hobbyist photographer

Back to Overview