MVC Role based authorization with Azure Active Directory (AAD)

windows-azure-active-directory

[Using Visual Studio 2015]

If you’re struggling to get the [Authorize(Roles=””)] attribute working on your controllers or actions, hopefully this blog will fill in the gaps for you. Without a few extra pieces of configuration and code, changing the Authorize attributes anywhere to include the Roles parameter will result in your web app looping when trying to authorize.

The out of the box experience of setting up an Azure MVC website with the authentication option of “Work and School Accounts” sets up the authentication and general authorization for your project, and doesn’t take much to configure successfully. If you make sure you setup your domain in AAD first, and ensure you’ve logged into your subscription properly from within Visual Studio, the whole process is taken care of for you by the wizards, and in the Change Authentication dialog shown below, you’ll be presented with a list of your provisioned AD domains to choose from.

If you want your application to be able to read the AD information (using the graph API to discover details about users, roles and groups provisioned within your directory), then you should check on the Read Directory Data option. Alternatively, you can enable this using the Azure management portal later.

If you’ve chosen the “Host in the cloud” option, chosen Web App as your hosting method, then the wizard will prompt you for details for your new site, including the options to create a new Resource Group, a new SQL Server, App Service Plan, and so on. I love using resource groups, especially for quick proof of concepts or trials, you can set up the whole environment to play with, then clean it up really easily from one place.

Once the wizard’s finished, and the template’s been run, here’s what you should now have:

  • A VS2015 solution with an MVC web app project in it, that builds and runs locally
  • An app.config that contains your application’s ClientId (a Guid) and Client Secret (a hash string), among other attributes prefixed with “ida:”, these all enable your app to tell the AD provider how to login and authenticate against the directory you choose.

In your Azure subscription:

  • A resource group containing:
    • An app service plan
    • An AppInsights instance
    • Your web application itself

  • And if you look inside your Directory in the Azure management portal (not the preview one), you will see a new entry listed underneath the “Applications” tab, with the name of your new web application

Of course, we haven’t deployed yet to Azure, so although the resources have been provisioned, they won’t yet do anything.

The ClientId and secret are configured in the Applications tab in the Azure Management portal (Directories/<your domain>/Applications/<your web app>). The tenant Id is the GUID relating to your directory, and you can find it in the URL when looking inside your directory in the management portal – you will only need to change this manually if you change the domain (directory) that you want to use, the two are attributes of the same object.

So far so good – everything’s straightforward. If you run the application now, because we’ve said we want it secured, the first thing that happens is the app will try to authenticate you against the AAD domain. Hence, it will redirect to login.microsoftonline.com (“iad:AADInstance”), and use the TenantId to apply any customisations to the login user interface (if you’ve got AAD premium, there are many more options available on the Directory configurations tab to do this). The authorisation is all controlled through the AuthorizeAttribute, which is set on each controller class.

In order to support Roles authentication with AAD, you need to do the following:

  1. Define the roles declaratively in the application’s manifest file
  2. Ensure the authorization calls pull in the roles as part of the claims for the current user

The manifest file can be found in the Management Portal, again under Active Directory, find your application and click on it, then you will see a Manage Manifest button in the toolbar at the bottom.

This button gives two options – download or upload. The manifest is a JSON file, and contains all the information AAD has about your application. You will notice the first time you edit this file, that there’s an empty array of “appRole” declared. This is where you need to add your roles, then re-upload the manifest. On upload, Azure will parse the file and check it’s valid, then apply the changes to your application.

Here’s one I made earlier that defines two application roles – Internal Admin and Operator.

  "appRoles": [
    {
      "allowedMemberTypes": [
        "User"
      ],
      "description": "Administrative staff who have access across all customer data sets",
      "displayName": "Internal Admin",
      "id": "3332d0ed-5b07-489d-8d8b-88ffab95d945",
      "isEnabled": true,
      "value": "internaladmin"
    },
    {
      "allowedMemberTypes": [
        "User"
      ],
      "description": "An operation staff member responsible for monitoring multiple systems",
      "displayName": "Operator",
      "id": "9be94e89-3c08-4d70-a693-16a236e32084",
      "isEnabled": true,
      "value": "operator"
    }
  ],

Now the code change to ensure the roles claims are processed by the Authorization request and added to the user’s identity. The highlighted code needs to be added to the Startup.Auth.cs file:

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
        ClientId = clientId,
        Authority = Authority,
        PostLogoutRedirectUri = postLogoutRedirectUri,

        TokenValidationParameters = new system.IdentityModel.Tokens.TokenValidationParameters()
        {
          ValidateIssuer = false,
          RoleClaimType = "roles"
        },

        Notifications = new OpenIdConnectAuthenticationNotifications()
        {
            // If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
            AuthorizationCodeReceived = (context) => 
            {
                var code = context.Code;
                ClientCredential credential = new ClientCredential(clientId, appKey);
                string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;

              AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID, tokenCache));

              AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);

              return Task.FromResult(0);
            }
        }
    });

Now, you can use Authorize with the Roles parameter, or use User.IsInRole() – note the code snippet below is just to show you a call to IsInRole – obviously the function will only ever get called if the user’s in that role, because we’ve declared it in the Authorize attribute.

The final piece of the puzzle – what happens if the user’s not in that role, but the controller’s action method is called? Well, in order to do a redirect to your own handler, you will need to customise your own AuthorizeAttribute class – a simple example would be –

 
public class AuthorizeExAttribute : AuthorizeAttribute
{
  protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
  {
    filterContext.Result = new RedirectResult("~/unauthorized");
  }
}

I’d recommend passing the filterContext.RequestContext.HttpContext.Request.UrlReferrer to your redirected page, so that you can give some kind of meaningful error message to the client’s browser. Don’t forget to replace all occurrences of [Authorize] with [AuthorizeEx] or whatever you’ve called your custom attribute.

  • Asdrubal Chirinos

    Where do I configure those Roles at Azure AD?

  • youngr6

    Hi Asdrubal, if you mean define which user a role belongs to, you can do that from the classic portal – go to Active Directory, choose your directory, click on users, then click on the user, the drop down will appear in “role” as “organisational role”. The roles will be those configured in your manifest file.