Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / Azure

Role Based Access Control with Azure Active Directory & MVC

5.00/5 (4 votes)
2 Apr 2014CPOL5 min read 42.9K   5  
Integrating Roles Based Access Control with the Azure Active Directory Graph API in a ASP.net MVC application

Introduction

Many applications and websites need to control access to certain areas for certain groups of users, for example credit control users may see credit history, where as, the order processor would only need to see there account has credit and is OK to order.

This is commonly implemented by one of the following methods

  • Calling the IPrincipal.IsInRole(role) method.
  • <span style="color: rgb(17, 17, 17); font-family: 'Segoe UI', Arial, sans-serif; font-size: 14px;">Place a </span>[PrincipalPermission(user, role)] attribute on the class or method.
  • Create an instance of PrincipalPermission(user, role) and calling Demand()

With the groups coming from an active directory or database using the asp.net role providers, for more information see here: http://msdn.microsoft.com/en-us/library/aa478950.aspx

If you want to make use of the Azure active directory and use active directory groups to enable roles based authentication then this isn't natively and you will need to query the "Azure Active Directory Graph API"

For more information on the Azure active directory and its benefits you can check out the Azure site.

From here on in were going to discuss how to use the nuget package Azure.ActiveDirectory to easily add roles authentication to our application.

Background

By default the visual studio project wizard sets up the project to use entity framework as a ValidatingIssuerNameRegistry as SQL storage is a little expensive in azure we are going to switch this out to the ridiculously cheap table storage instead. Over time I hope to improve the nuget experience to reduce the amount of manual steps required. The rest of this article assumes you have a azure account and an active directory set up, alternatively you can easily create a new one from the management portal.

In the admin portal I have created 2 users and 2 group within the directory codeproject.onmicrosoft.com

  • admin@codeproject.onmicrosoft.com as a member of "Admin" group and with the "Global Administrator Role" this will allow us to create the application within the active directory when we are running the visual studio wizard.
  • member@codeproject.onmicrosoft.com as a member of "Member" group
We will use the users to test the roles authentication is working in our MVC sample application.

Using the code

Create a new ASP.NET project, select the MVC project template and select the "Change Authentication" button to configure the MVC project to use our active directory. As we want to enable the Azure graph api to return our groups, we need to ensure we have selected an option that allows reading directory data.

Image 1

I have entered the name codeproject.onmicrosoft.com of my active directory and selected "Single Sign On, Read directory data" to allow access to the graph.

The App ID URI is used to identify you application in the azure directory and by default this is automatically generated from your domain name and project name.

When you click OK sign in with your admin user.

Once visual studio has created the project you need to run the following commands in package manager console.

  • Install-Package Azure.ActiveDirectory
  • Install-Package Microsoft.WindowsAzure.ConfigurationManager (for some reason the nuget dependency was not added)
  • Update-Package (optionally update to the latest bits)

Now we need to make some modifications to the web.config to register our storage account and AzureTableIssuerNameRegistry. The AzureTableIssuerNameRegistry implements a ValidatingIssuerNameRegistry on Azure table storage as opposed to the entity framework provider registered by default.

Configure the IssuerNameRegistry

By default the config will point to a DatabaseIssuerNameRegistry created by the wizard within the application.

XML
<system.identityModel>
  <identityConfiguration>
    <issuerNameRegistry type="RolesBasedAuthenticationSample.Utils.DatabaseIssuerNameRegistry, RolesBasedAuthenticationSample" />
    <audienceUris>
      <add value="https://codeproject.onmicrosoft.com/RolesBasedAuthenticationSample" />
    </audienceUris>
  <!--
     rest of details ommited
     ..
  -->
</system.identityModel>

Update the issuerNameRegistry to point to the package registry

XML
<issuerNameRegistry type="Azure.ActiveDirectory.AzureTableIssuerNameRegistry, Azure.ActiveDirectory" />  

Now we have registered our new registry we can delete the folowing files from our project as they are no longer needed.

  • Models\TenantDbContext.cs
  • Models\TenantReistrationModels.cs
  • Models\HomeViewModel.cs
  • Utils\DatabaseIssuerNameRegistry

In order for the AzureTableIssuerNameRegistry to work we need to set appSettings or CloudSettings configuration key Identity.StorageConnectionString to a storage connection string and should look similar to

<add key="Identity.StorageConnectionString" value="DefaultEndpointsProtocol=https;AccountName=[name];AccountKey=[key]" /> 

the correct connection string for your storage account can be easily found within the Azure management portal, or, visual studio's server explorer properties panel.

Update the IdentityConfig.cs

The IdentityConfig created under the App_Start folder will need to be updated to refresh its keys from Azure instead of the default EF registry. Update the method RefreshValidationSettings()

C#
public static void RefreshValidationSettings()
       {
           string metadataLocation = ConfigurationManager.AppSettings["ida:FederationMetadataLocation"];
           Azure.ActiveDirectory.AzureTableIssuerNameRegistry.RefreshKeys(metadataLocation);
       }

Within the ConfigureIdentity() method we need to tell the identity configuration to get our role claims from the Azure graph. We do this by adding the following line

C#
FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager = new Azure.ActiveDirectory.GraphClaimsAuthenticationManager();

Now this is place we should be able to press F5 and run the app and sign in when prompted we should be logged in and presented with the default site.

Image 2

HomeController.cs

By default the HomeController has a bunch of variables declared and makes a request to the graph api which returns Json that is hydrated to the previously deleted UserProfile class contained within the HomeViewModel.cs.

Were going to update the HomeController to use the IActiveDirectoryGraphClient and pass the current user to the UserProfile view.

C#
using System.Web.Mvc;
using Azure.ActiveDirectory;

namespace RolesBasedAuthenticationSample.Controllers
{
    public class HomeController : Controller
    {
        private readonly IActiveDirectoryGraphClient _graphClient = new GraphClient();

        [Authorize]
        public ActionResult UserProfile()
        {
            return View(_graphClient.CurrentUser);
        }

        public ActionResult Index()
        {
            return View();
        }

        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }
       
    }
} 
UserProfie.cshtml

As we are no longer using the UserProfile created within the ASP.NET project we need to quickly update the view to use our User object.

ASP.NET
@model Azure.ActiveDirectory.AzureGraphService.Microsoft.WindowsAzure.ActiveDirectory.User
@{
    ViewBag.Title = "User Profile";
}

<h2>@ViewBag.Title.</h2>

<table class="table table-bordered table-striped">
    <tr>
        <td>Display Name</td>
        <td>@Html.DisplayFor(m=>m.displayName)</td>
    </tr>
    <tr>
        <td>First Name</td>
        <td>@Html.DisplayFor(m=>m.givenName)</td>
    </tr>
    <tr>
        <td>Last Name</td>
        <td>@Html.DisplayFor(m=>m.surname)</td>
    </tr>
</table>  

That's it when you run the application you should still see the screen above. Note the package has some data annotations to provide the column names.

Adding Roles

In order to demonstate roles we are going to add a Admin page and a Member page for each of the groups we created earlier

Update HomeController.cs

Add 2 actions for the new views

C#
[PrincipalPermission(SecurityAction.Demand, Role = "Admin")]
      public ActionResult Admin()
      {
          ViewBag.Message = "Your admin page.";

          return View();
      }

      [PrincipalPermission(SecurityAction.Demand, Role="Member")]
      public ActionResult Member()
      {
          ViewBag.Message = "Your member page.";

          return View();
      }
Update shared _Layout.cshtml

Add 2 menu links for the new pages to the shared layout file, Find the navbar with the top menu action links and change the <ul> list to

ASP.NET
<ul class="nav navbar-nav">
                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                    <li>@Html.ActionLink("About", "About", "Home")</li>
                    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
                    @if (User.IsInRole("Member"))
                    {
                        <li>@Html.ActionLink("Member", "Member", "Home")</li>
                    }
                    @if (User.IsInRole("Admin"))
                    {
                        <li>@Html.ActionLink("Admin", "Admin", "Home")</li>
                    }
                    </ul> 
Create Admin.cshtml partial view
C#
@{
    ViewBag.Title = "Admin";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>

<p>Welcome to your admin page.</p> 
Create Member.cshtml partial view
C#
@{
    ViewBag.Title = "Member";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>

<p>Welcome to your member page.</p> 

Now this is in place when you run the app and login as the Admin user you should see the Admin menu link and be able to navigate to the admin page.

Admin logged in

Admin Page

If we try navigating to the member page in the address bar the Authentication system will disallow it

Admin Security Exception

If we login with the member user we can see the Admin menu has gone and is replaced with the user menu.

Image 6

History

2nd April 2014 - First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)