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.
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.
<system.identityModel>
<identityConfiguration>
<issuerNameRegistry type="RolesBasedAuthenticationSample.Utils.DatabaseIssuerNameRegistry, RolesBasedAuthenticationSample" />
<audienceUris>
<add value="https://codeproject.onmicrosoft.com/RolesBasedAuthenticationSample" />
</audienceUris>
</system.identityModel>
Update the issuerNameRegistry
to point to the package registry
<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()
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
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.
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.
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.
@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
[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
<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
@{
ViewBag.Title = "Admin";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>
<p>Welcome to your admin page.</p>
Create Member.cshtml partial view
@{
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.
If we try navigating to the member page in the address bar the Authentication system will disallow it
If we login with the member user we can see the Admin menu has gone and is replaced with the user menu.
History
2nd April 2014 - First version