ASP.NET MVC Forms Authentication + Authorize Attribute + Simple Roles

asp.net MvcForms AuthenticationAuthorization

asp.net Mvc Problem Overview


I'm trying to add simple Authentication and Authorization to an ASP.NET MVC application.

I'm just trying to tack on some added functionality to the basic Forms Authentication (due to simplicity and custom database structure)

Assuming this is my database structure: User: username password role (ideally some enum. Strings if need be. Currently, user only has ONE role, but this might change)

High Level Problem: Given the above database structure, I would like to be able to do the following:

  • Simple Login using Forms Authentication
  • Decorate my actions with: [Authorize(Roles={ MyRoles.Admin, MyRoles.Member})]
  • Use roles in my Views (to determine links to display in some partials)

Currently, all I'm really sure of is how to Authenticate. After that I'm lost. I'm not sure at which point do I grab the user role (login, every authorization?). Since my roles may not be strings, I'm not sure how they will fit in with the User.IsInRole().

Now, I'm asking here because I haven't found a "simple" accomplish what I need. I have seen multiple examples.

For Authentication:

  • We have simple user validation that checks the database and "SetAuthCookie"
  • Or we override the Membership provider and do this inside of ValidateUser In either of these, I'm not sure how to tack on my simple user Roles, so that they work with the: HttpContext.Current.User.IsInRole("Administrator") Furthermore, I'm not sure how to modify this to work with my enum values.

For Authorization, I've seen:

  • Deriving AuthorizeAttribute and implementing AuthorizeCore OR OnAuthorization to handle roles?
  • Implementing IPrincipal?

Any assistance would be greatly appreciated. However, I fear I may need a lot of detail, because none of what I've Googled seems to fit with what I need to do.

asp.net Mvc Solutions


Solution 1 - asp.net Mvc

I think I've implemented something similar.
My solution, based on NerdDinner tutorial, is following.

When you sign the user in, add code like this:

var authTicket = new FormsAuthenticationTicket(
    1,                             // version
    userName,                      // user name
    DateTime.Now,                  // created
    DateTime.Now.AddMinutes(20),   // expires
    rememberMe,                    // persistent?
    "Moderator;Admin"                        // can be used to store roles
    );

string encryptedTicket = FormsAuthentication.Encrypt(authTicket);

var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);

Add following code to Global.asax.cs:

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
    HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
    if (authCookie == null || authCookie.Value == "")
        return;

    FormsAuthenticationTicket authTicket;
    try
    {
        authTicket = FormsAuthentication.Decrypt(authCookie.Value);
    }
    catch
    {
        return;
    }

    // retrieve roles from UserData
    string[] roles = authTicket.UserData.Split(';');

    if (Context.User != null)
        Context.User = new GenericPrincipal(Context.User.Identity, roles);
}

After you've done this, you can use [Authorize] attribute in your controller action code:

[Authorize(Roles="Admin")]
public ActionResult AdminIndex ()

Please let me know if you have further questions.

Solution 2 - asp.net Mvc

Build a custom AuthorizeAttribute that can use your enums rather than strings. When you need to authorise, convert the enums into strings by appending the enum type name + the enum value and use the IsInRole from there.

To add roles into an authorised user you need to attach to the HttpApplication AuthenticateRequest event something like the first code in http://www.eggheadcafe.com/articles/20020906.asp ( but invert the massively nested if statements into guard clauses!).

You can round-trip the users roles in the forms auth cookie or grab them from the database each time.

Solution 3 - asp.net Mvc

I did something like this:

  • Use the Global.asax.cs to load the roles you want to compare in session,cache, or application state, or load them on the fly on the ValidateUser controller

Assign the [Authorize] attribute to your controllers, you want to require authorization for

 [Authorize(Roles = "Admin,Tech")]

or to allow access, for example the Login and ValidateUser controllers use the below attribute

 [AllowAnonymous] 

My Login Form

<form id="formLogin" name="formLogin" method="post" action="ValidateUser">
<table>
  <tr>
    <td>
       <label for="txtUserName">Username: (AD username) </label>
    </td>
    <td>
       <input id="txtUserName" name="txtUserName" role="textbox" type="text" />
    </td>
  </tr>
  <tr>
     <td>
         <label for="txtPassword">Password: </label>
     </td>
     <td>
         <input id="txtPassword" name="txtPassword" role="textbox" type="password" />
     </td>
  </tr>
  <tr>
      <td>
         <p>
           <input id="btnLogin" type="submit" value="LogIn" class="formbutton" />
        </p>
      </td>
  </tr>
</table>
       @Html.Raw("<span id='lblLoginError'>" + @errMessage + "</span>")
</form>

Login Controller and ValidateUser controller invoked from the Form post

Validate user is authentication via a WCF service that validates against the Windows AD Context local to the service, but you can change this to your own authentication mechanism

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using System.Security.Principal;
using MyMVCProject.Extensions;
namespace MyMVCProject.Controllers
{
public class SecurityController : Controller
{
    [AllowAnonymous]
    public ActionResult Login(string returnUrl)
    {
        Session["LoginReturnURL"] = returnUrl;
        Session["PageName"] = "Login";
        return View("Login");
    }
    [AllowAnonymous]
    public ActionResult ValidateUser()
    {
        Session["PageName"] = "Login";
        ViewResult retVal = null;
        string loginError = string.Empty;
        HttpContext.User = null;

        var adClient = HttpContext.Application.GetApplicationStateWCFServiceProxyBase.ServiceProxyBase<UserOperationsReference.IUserOperations>>("ADService").Channel;

        var username = Request.Form["txtUserName"];
        var password = Request.Form["txtPassword"];
        
        //check for ad domain name prefix
        if (username.Contains(@"\"))
          username = username.Split('\\')[1];

        //check for the existence of the account 
        var acctReq = new UserOperationsReference.DoesAccountExistRequest();
        acctReq.userName = username;
        //account existence result
        var accountExist = adClient.DoesAccountExist(acctReq);
        if (!accountExist.DoesAccountExistResult)
        {
            //no account; inform the user
            return View("Login", new object[] { "NO_ACCOUNT", accountExist.errorMessage });
        }
        //authenticate
        var authReq = new UserOperationsReference.AuthenticateRequest();
        authReq.userName = username;
        authReq.passWord = password;
        var authResponse = adClient.Authenticate(authReq);
        String verifiedRoles = string.Empty;
        //check to make sure the login was as success against the ad service endpoint
        if (authResponse.AuthenticateResult == UserOperationsReference.DirectoryServicesEnumsUserProperties.SUCCESS)
        {
            Dictionary<string, string[]> siteRoles = null;

            //get the role types and roles
            if (HttpContext.Application["UISiteRoles"] != null)
                siteRoles = HttpContext.Application.GetApplicationState<Dictionary<string, string[]>>("UISiteRoles");

            string groupResponseError = string.Empty;
            if (siteRoles != null && siteRoles.Count > 0)
            {
                //get the user roles from the AD service
                var groupsReq = new UserOperationsReference.GetUsersGroupsRequest();
                groupsReq.userName = username;
                //execute the service method for getting the roles/groups
                var groupsResponse = adClient.GetUsersGroups(groupsReq);
                //retrieve the results
                if (groupsResponse != null)
                {
                    groupResponseError = groupsResponse.errorMessage;
                    var adRoles = groupsResponse.GetUsersGroupsResult;

                    if (adRoles != null)
                    {
                        //loop through the roles returned from the server
                        foreach (var adRole in adRoles)
                        {
                            //look for an admin role first
                            foreach (var roleName in siteRoles.Keys)
                            {
                                var roles = siteRoles[roleName].ToList();
                                foreach (var role in roles)
                                {
                                    if (adRole.Equals(role, StringComparison.InvariantCultureIgnoreCase))
                                    {
                                        //we found a role, stop looking
                                        verifiedRoles += roleName + ";";
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            if (String.IsNullOrEmpty(verifiedRoles))
            {
                //no valid role we need to inform the user
                return View("Login", new object[] { "NO_ACCESS_ROLE", groupResponseError });
            }

            if (verifiedRoles.EndsWith(";"))
                verifiedRoles = verifiedRoles.Remove(verifiedRoles.Length - 1, 1);

            //all is authenticated not build the auth ticket
            var authTicket = new FormsAuthenticationTicket(
            1,                             // version
            username,                      // user name
            DateTime.Now,                  // created
            DateTime.Now.AddMinutes(20),  // expires
            true,                    // persistent?
           verifiedRoles   // can be used to store roles
            );

            //encrypt the ticket before adding it to the http response
            string encryptedTicket = FormsAuthentication.Encrypt(authTicket);

            var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
            Response.Cookies.Add(authCookie);

            Session["UserRoles"] = verifiedRoles.Split(';');

            //redirect to calling page
            Response.Redirect(Session["LoginReturnURL"].ToString());
        }
        else
        {
            retVal = View("Login", new object[] { authResponse.AuthenticateResult.ToString(), authResponse.errorMessage });
        }

        return retVal;
    }
}

}

User is authenticated now create the new Identity

protected void FormsAuthentication_OnAuthenticate(Object sender,     FormsAuthenticationEventArgs e)
    {
        if (FormsAuthentication.CookiesSupported == true)
        {
            HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
            if (authCookie == null || authCookie.Value == "")
                return;

            FormsAuthenticationTicket authTicket = null;
            try
            {
                authTicket = FormsAuthentication.Decrypt(authCookie.Value);
            }
            catch
            {
                return;
            }

            // retrieve roles from UserData
            if (authTicket.UserData == null)
                return;

            //get username from ticket
            string username = authTicket.Name;

            Context.User = new GenericPrincipal(
                      new System.Security.Principal.GenericIdentity(username, "MyCustomAuthTypeName"), authTicket.UserData.Split(';'));
        }
    }

On my site at the the top of my _Layout.cshtml I have something like this

 {
  bool authedUser = false;
  if (User != null && User.Identity.AuthenticationType == "MyCustomAuthTypeName" && User.Identity.IsAuthenticated)
   {
      authedUser = true;
   }
 }

Then in the body

        @{
         if (authedUser)
          {
            <span id="loggedIn_userName">
                <label>User Logged In: </label>@User.Identity.Name.ToUpper()
            </span>
          }
          else
          {
            <span id="loggedIn_userName_none">

                <label>No User Logged In</label>
            </span>
          }
        }

Solution 4 - asp.net Mvc

Add your users to the table "users in roles". Use the stored procedure "addusertorole" (something like that) in your code to add to various roles. You can create the roles very simply in the "roles" table.

Your tables to use: User, UsersInRole, Roles

Use the built in Stored Procs to manipulate those tables. Then all you have to do is add the attribute.

For example you can have an "Admin" attribute on a view that selects a user and adds them to a role. You can use the stored proc to add that user to the role.

Solution 5 - asp.net Mvc

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using SISWEBBSI.Models.Model;
using SISWEBBSI.Models.Model.Entities;
using SISWEBBSI.Models.ViewModel;

namespace SISWEBBSI.Controllers.ActionFilter
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public sealed class RequerAutorizacao : ActionFilterAttribute
    {
        public Grupo.Papeis[] Papeis = {} ;
        public string ViewName { get; set; }
        public ViewDataDictionary ViewDataDictionary { get; set; }
        public AcessoNegadoViewModel AcessoNegadoViewModel { get; set; }

        public override void OnActionExecuting(ActionExecutingContext FilterContext)
        {
            if (!FilterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                string UrlSucesso = FilterContext.HttpContext.Request.Url.AbsolutePath;
                string UrlRedirecionar = string.Format("?ReturnUrl={0}", UrlSucesso);
                string UrlLogin = FormsAuthentication.LoginUrl + UrlRedirecionar;
                FilterContext.HttpContext.Response.Redirect(UrlLogin, true);
            }
            else
            {
                if (Papeis.Length > 0)
                {
                    //Papel ADMINISTRADOR sempre terá acesso quando alguma restrição de papeis for colocada.
                    int NovoTamanho = Papeis.Count() + 1;
                    Array.Resize(ref Papeis, NovoTamanho);
                    Papeis[NovoTamanho - 1] = Grupo.Papeis.ADMINISTRADOR;
                    UsuarioModel Model = new UsuarioModel();
                    if (!Model.UsuarioExecutaPapel(FilterContext.HttpContext.User.Identity.Name, Papeis))
                    {
                        ViewName = "AcessoNegado";
                        String Mensagem = "Você não possui privilégios suficientes para essa operação. Você deve estar nos grupos que possuem";
                        if(Papeis.Length == 1)
                        {
                            Mensagem = Mensagem + " o papel: <BR/>";
                        }
                        else if (Papeis.Length > 1)
                        {
                            Mensagem = Mensagem + " os papéis: <BR/>";
                        }
                        
                        foreach (var papel in Papeis)
                        {
                            Mensagem = Mensagem + papel.ToString() + "<br/>";
                        }
                        AcessoNegadoViewModel = new AcessoNegadoViewModel();
                        AcessoNegadoViewModel.Mensagem = Mensagem;
                        ViewDataDictionary = new ViewDataDictionary(AcessoNegadoViewModel);
                        FilterContext.Result = new ViewResult { ViewName = ViewName, ViewData = ViewDataDictionary };
                        return;
                    }
                }
            }
        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKevinView Question on Stackoverflow
Solution 1 - asp.net MvcyinnerView Answer on Stackoverflow
Solution 2 - asp.net MvcNealView Answer on Stackoverflow
Solution 3 - asp.net MvcdynamiclynkView Answer on Stackoverflow
Solution 4 - asp.net MvcMike McClintockView Answer on Stackoverflow
Solution 5 - asp.net MvcMarlonView Answer on Stackoverflow