title: MVC Custom Authentication, Authorization, and Roles Implementation
body: <p>Bear with me as I provide details for the issue...</p>
  
  <p>I've got an MVC site, using <code>FormsAuthentication</code> and custom service classes for Authentication, Authorization, Roles/Membership, etc. </p>
  
  <h3>Authentication</h3>
  
  <p>There are three ways to sign-on: <em>(1) Email + Alias</em>, <em>(2) OpenID</em>, and <em>(3) Username + Password</em>. All three get the user an auth cookie and start a session. The first two are used by visitors (session only) and the third for authors/admin with db accounts. </p>
  
  <pre><code>public class BaseFormsAuthenticationService : IAuthenticationService
  {
      // Disperse auth cookie and store user session info.
      public virtual void SignIn(UserBase user, bool persistentCookie)
      {
          var vmUser = new UserSessionInfoViewModel { Email = user.Email, Name = user.Name, Url = user.Url, Gravatar = user.Gravatar };
  
          if(user.GetType() == typeof(User)) {
              // roles go into view model as string not enum, see Roles enum below.
              var rolesInt = ((User)user).Roles;
              var rolesEnum = (Roles)rolesInt;
              var rolesString = rolesEnum.ToString();
              var rolesStringList = rolesString.Split(',').Select(role =&gt; role.Trim()).ToList();
              vmUser.Roles = rolesStringList;
          }
  
          // i was serializing the user data and stuffing it in the auth cookie
          // but I'm simply going to use the Session[] items collection now, so 
          // just ignore this variable and its inclusion in the cookie below.
          var userData = "";
  
          var ticket = new FormsAuthenticationTicket(1, user.Email, DateTime.UtcNow, DateTime.UtcNow.AddMinutes(30), false, userData, FormsAuthentication.FormsCookiePath);
          var encryptedTicket = FormsAuthentication.Encrypt(ticket);
          var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket) { HttpOnly = true };
          HttpContext.Current.Response.Cookies.Add(authCookie);
          HttpContext.Current.Session["user"] = vmUser;
      }
  }
  </code></pre>
  
  <h3>Roles</h3>
  
  <p>A simple flags enum for permissions:</p>
  
  <pre><code>[Flags]
  public enum Roles
  {
      Guest = 0,
      Editor = 1,
      Author = 2,
      Administrator = 4
  }
  </code></pre>
  
  <p>Enum extension to help enumerate flag enums (wow!).</p>
  
  <pre><code>public static class EnumExtensions
  {
      private static void IsEnumWithFlags&lt;T&gt;()
      {
          if (!typeof(T).IsEnum)
              throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof (T).FullName));
          if (!Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
              throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
      }
  
      public static IEnumerable&lt;T&gt; GetFlags&lt;T&gt;(this T value) where T : struct
      {
          IsEnumWithFlags&lt;T&gt;();
          return from flag in Enum.GetValues(typeof(T)).Cast&lt;T&gt;() let lValue = Convert.ToInt64(value) let lFlag = Convert.ToInt64(flag) where (lValue &amp; lFlag) != 0 select flag;
      }
  }
  </code></pre>
  
  <h3>Authorization</h3>
  
  <p>Service offers methods for checking an authenticated user's roles.</p>
  
  <pre><code>public class AuthorizationService : IAuthorizationService
  {
      // Convert role strings into a Roles enum flags using the additive "|" (OR) operand.
      public Roles AggregateRoles(IEnumerable&lt;string&gt; roles)
      {
          return roles.Aggregate(Roles.Guest, (current, role) =&gt; current | (Roles)Enum.Parse(typeof(Roles), role));
      }
  
      // Checks if a user's roles contains Administrator role.
      public bool IsAdministrator(Roles userRoles)
      {
          return userRoles.HasFlag(Roles.Administrator);
      }
  
      // Checks if user has ANY of the allowed role flags.
      public bool IsUserInAnyRoles(Roles userRoles, Roles allowedRoles)
      {
          var flags = allowedRoles.GetFlags();
          return flags.Any(flag =&gt; userRoles.HasFlag(flag));
      }
  
      // Checks if user has ALL required role flags.
      public bool IsUserInAllRoles(Roles userRoles, Roles requiredRoles)
      {
          return ((userRoles &amp; requiredRoles) == requiredRoles);
      }
  
      // Validate authorization
      public bool IsAuthorized(UserSessionInfoViewModel user, Roles roles)
      {
          // convert comma delimited roles to enum flags, and check privileges.
          var userRoles = AggregateRoles(user.Roles);
          return IsAdministrator(userRoles) || IsUserInAnyRoles(userRoles, roles);
      }
  }
  </code></pre>
  
  <p>I chose to use this in my controllers via an attribute:</p>
  
  <pre><code>public class AuthorizationFilter : IAuthorizationFilter
  {
      private readonly IAuthorizationService _authorizationService;
      private readonly Roles _authorizedRoles;
  
      /// &lt;summary&gt;
      /// Constructor
      /// &lt;/summary&gt;
      /// &lt;remarks&gt;The AuthorizedRolesAttribute is used on actions and designates the 
      /// required roles. Using dependency injection we inject the service, as well 
      /// as the attribute's constructor argument (Roles).&lt;/remarks&gt;
      public AuthorizationFilter(IAuthorizationService authorizationService, Roles authorizedRoles)
      {
          _authorizationService = authorizationService;
          _authorizedRoles = authorizedRoles;
      }
  
      /// &lt;summary&gt;
      /// Uses injected authorization service to determine if the session user 
      /// has necessary role privileges.
      /// &lt;/summary&gt;
      /// &lt;remarks&gt;As authorization code runs at the action level, after the 
      /// caching module, our authorization code is hooked into the caching 
      /// mechanics, to ensure unauthorized users are not served up a 
      /// prior-authorized page. 
      /// Note: Special thanks to TheCloudlessSky on StackOverflow.
      /// &lt;/remarks&gt;
      public void OnAuthorization(AuthorizationContext filterContext)
      {
          // User must be authenticated and Session not be null
          if (!filterContext.HttpContext.User.Identity.IsAuthenticated || filterContext.HttpContext.Session == null)
              HandleUnauthorizedRequest(filterContext);
          else {
              // if authorized, handle cache validation
              if (_authorizationService.IsAuthorized((UserSessionInfoViewModel)filterContext.HttpContext.Session["user"], _authorizedRoles)) {
                  var cache = filterContext.HttpContext.Response.Cache;
                  cache.SetProxyMaxAge(new TimeSpan(0));
                  cache.AddValidationCallback((HttpContext context, object o, ref HttpValidationStatus status) =&gt; AuthorizeCache(context), null);
              }
              else
                  HandleUnauthorizedRequest(filterContext);             
          }
      }
  </code></pre>
  
  <p>I decorate Actions in my Controllers with this attribute, and like Microsoft's <code>[Authorize]</code> no params means let in anyone authenticated (for me it is Enum = 0, no required roles).</p>
  
  <p>That about wraps up the background info (phew)... and writing all this out I answered my first question. At this point I am curious about the appropriateness of my setup:</p>
  
  <ol>
  <li><p>Do I need to manually snag the auth cookie and populate the FormsIdentity principal for the <code>HttpContext</code> or should that be automatic?</p></li>
  <li><p>Any issues with checking authentication within the attribute/filter <code>OnAuthorization()</code>?</p></li>
  <li><p>What are tradeoffs in using <code>Session[]</code> to store my view model vs. serializing it within the auth cookie?</p></li>
  <li><p>Does this solution seem to follow the 'separation of concerns' ideals well enough? (Bonus as it is more opinion-oriented question)</p></li>
  </ol>