The CAS authentication service is a single sign-on solution for web services used by a number of universities. When authenticating with CAS, the server has the option of embedding a list of user roles inside the encrypted authentication cookie. In this tutorial, I’ll explain how to write a custom role provider in ASP.NET MVC applications to extract the roles passed from the authentication server and integrate them with ASP.NET role authorization.
If you don’t already have your app configured to use CAS authentication, check out my post on getting started with the .NET CAS Client.
Create the CAS Role Provider
To integrate with ASP.NET web security, we need a class that implements the abstract class RoleProvider. Note that in the example below, I leave some of the methods as not implemented since all are not necessary to get started authorizing with CAS roles. I've named my class CasRoleProvider and placed it inside a folder named DataAccess.
On my authorization server, the roles are passed under a property named roleAttributeName. This property may differ on your server, so be sure to check with your systems administrator and change the constant at the top of the class accordingly.
using DotNetCasClient; using DotNetCasClient.Security; using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Configuration.Provider; using System.Linq; using System.Web.Security; namespace SurplusPrototype.DataAccess { public class CasRoleProvider : RoleProvider { public const string ROLE_ATTRIBUTE_NAME = "roleAttributeName"; private readonly static IList EMPTY_LIST = new List(0).AsReadOnly(); private string roleAttribute; public override void Initialize(string name, NameValueCollection config) { if (config == null) { throw new ArgumentNullException("config"); } // Assign the provider a default name if it doesn't have one if (String.IsNullOrEmpty(name)) { name = "CasAssertionRoleProvider"; } base.Initialize(name, config); roleAttribute = config[ROLE_ATTRIBUTE_NAME]; if (roleAttribute == null) { throw new ProviderException(ROLE_ATTRIBUTE_NAME + " is required but has not been provided."); } if (roleAttribute == string.Empty) { throw new ProviderException(ROLE_ATTRIBUTE_NAME + " roleAttribute must be non-empty string."); } } public override void AddUsersToRoles(string[] usernames, string[] roleNames) { throw new NotImplementedException(); } public override string ApplicationName { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override void CreateRole(string roleName) { throw new NotImplementedException(); } public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) { throw new NotImplementedException(); } public override string[] FindUsersInRole(string roleName, string usernameToMatch) { throw new NotImplementedException(); } public override string[] GetAllRoles() { IList roles = GetCurrentUserRoles(); if (roles is Array) { return (string[])roles; } string[] roleArray = new string[roles.Count]; for (int i = 0; i < roles.Count; i++) { roleArray[i] = roles[i]; } return roleArray; } public override string[] GetRolesForUser(string username) { if (CasAuthentication.CurrentPrincipal.Identity.Name != username) { throw new ProviderException("Cannot fetch roles for user other than that of current context."); } return GetAllRoles(); } public override string[] GetUsersInRole(string roleName) { throw new NotImplementedException(); } public override bool IsUserInRole(string username, string roleName) { if (CasAuthentication.CurrentPrincipal.Identity.Name != username) { throw new ProviderException("Cannot fetch roles for user other than that of current context."); } return GetCurrentUserRoles().Count > 0; } public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) { throw new NotImplementedException(); } public override bool RoleExists(string roleName) { throw new NotImplementedException(); } private IList GetCurrentUserRoles() { ICasPrincipal principal = CasAuthentication.CurrentPrincipal; if (principal == null) { return EMPTY_LIST; } HashSet roles = new HashSet(principal.Assertion.Attributes[roleAttribute]); if (roles == null) { roles = new HashSet(EMPTY_LIST); } return roles.ToList(); } } }
Configure role authorization
With the role provider created, we now need to configure the app to use the class for authorization. Open your Web.config file and add the following configuration under system.web. This will be below the forms authentication configuration.
<configuration> <system.web> <!-- CAS Configuration --> <roleManager defaultProvider="CasRoleProvider" enabled="true" cacheRolesInCookie="true"> <providers> <clear /> <add name="CasRoleProvider" type="SurplusPrototype.DataAccess.CasRoleProvider" roleAttributeName="virginiaTechAffiliation" /> </providers> </roleManager> <!-- /CAS Configuration --> </system.web> </configuration>
Force requests to authorize
The final step is to protect the controllers with the ASP.NET authorization attribute. This will force the application into the authentication pipeline, authorizing only those users that have the specified role. Under App_Start, open FilterConfig.cs and add a filter like the one below. Your role will be specific to your organization.