User authentication and authorisation in ASP.NET MVC

.Netasp.net MvcAuthenticationAuthorization

.Net Problem Overview


What is the best method for user authorisation/authentication in ASP.NET MVC?

I see there are really two approaches:

  • Use the built-in ASP.NET authorisation system.
  • Use a custom system with my own User, Permission, UserGroup tables etc.

I'd prefer the second option, because User is part of my domain model (and I have zero experience with ASP.NET's built-in stuff), but I'd really like to hear what people have been doing in this area.

.Net Solutions


Solution 1 - .Net

There is actually a third approach. The asp.net membership functionality is based on the provider model. You can write a custom provider, thus being able to provide your own implementation for how the data is stored, but retaining much of the benefit of asp.net membership.

Some articles on the subject:

http://msdn.microsoft.com/en-us/library/f1kyba5e.aspx

http://www.asp.net/learn/videos/video-189.aspx

http://www.15seconds.com/issue/050216.htm

http://davidhayden.com/blog/dave/archive/2007/10/11/CreateCustomMembershipProviderASPNETWebsiteSecurity.aspx

Solution 2 - .Net

Go with custom. MembershipProvider is way too heavy for my tastes. Yes it's possible to implement it in a simplified way, but then you get a really bad smell of NotSupportedException or NotImplementedException.

With a totally custom implementation you can still use IPrincipal, IIdentity and FormsAuth. And really how hard is it do your own login page and such?

Solution 3 - .Net

The easiest way is to use asp.net user names as role names. You can write your own authorizarion attribute to handle authorization:

public class CustomAuthorizationAttribute:AuthorizeAttribute
{
    public CustomAuthorizationAttribute():base()
    {
        Users = "registereduser";
    }
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        //You must check if the user has logged in and return true if he did that.
        return (bool)(httpContext.Session["started"]??false); 
     
    }
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        filterContext.HttpContext.Response.Redirect("SessionManagement/Index/?returningURL=" + 
            filterContext.HttpContext.Server.UrlEncode(filterContext.HttpContext.Request.Url.ToString()));
     
    }
    
}

The code must handle the AuthorizeCore to return true if the user has started the session, and HandleUnauthorizedRequest to redirect the user to the login page (optionaly you can attach the returning url).

In then controller methods that need authorization, set the attribute over them:

public class SecretPageController {
    [CustomAuthorizationAttribute]
    ActionResult Index() {
        //Method that requires authorization
        return View();
    }

}

Also set the authorization method to "Forms" in the web config.

Web.config:

  <authentication>
      <forms timeout="120"></forms>
  </authentication>

Controller:

public SessionManagementController:Controller {
    public ActionResult Index(string returningURL)
    {
        return View("Index", new SessionModel() { ReturningURL = returningURL});
    }
    [HttpPost]        
    public ActionResult Index(SessionModel mod)
    {
        if (UserAuthenticated(mod.UserName, mod.Password))
        {
            FormsAuthentication.SetAuthCookie("registereduser", false);
            if (mod.UrlRetorno != null)
            {
                return Redirect(mod.ReturningURL);                    
            }
            return RedirectToAction("Index", "StartPage");
        }
        mod.Error = "Wrong User Name or Password";
        return View(mod);
    }
    bool UserAuthenticated(string userName, string password) {
       //Write here the authentication code (it can be from a database, predefined users,, etc)
        return true;
    }

    public ActionResult FinishSession()
    {
        HttpContext.Session.Clear();//Clear the session information
        FormsAuthentication.SignOut();
        return View(new NotificacionModel() { Message = "Session Finished", URL = Request.Url.ToString() });
    }

}

In the Controller, when the user enters its user name and password, set the forms authentication cookie to TRUE (FormsAuthentication.SetAuthCookie("registereduser",true)), signaling the user name (registereduser in the example) to be authenticathed. Then the user signs out, tell ASP.NET to do so calling FormsAuthentication.SignOut().

Model:

class SessionModel {
    public string UserName {get;set;}
    public string Password {get;set;}
    public string Error {get;set;}
}  

Use a model to store the user data.

View (that presents the SessionModel type):

        <div class="editor-label">
            <%: Html.LabelFor(model => model.UserName) %>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(model => model.UserName) %>
            <%: Html.ValidationMessageFor(model => model.UserName) %>
        </div>
        
        <div class="editor-label">
            <%: Html.LabelFor(model => model.Password) %>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(model => model.Password) %>
            <%: Html.ValidationMessageFor(model => model.Password) %>
        </div>
        <div class="field-validation-error"><%:Model==null?"":Model.Error??"" %></div>
        <%:Html.HiddenFor(model=>model.ReturningURL) %>
        <input type="submit" value="Log In" />

Use a view to get the data. In this example, there is a hidden field to store the returning URL

I hope this helps (I had to translate the code, so I'm not sure if it is 100% correct).

Solution 4 - .Net

Yet another approach is to use ASP.NET membership for authentication, link your User class to ASP.NET members, and use your User class for more granular permissions. We do this, because it allows changing authentication providers very easily, while still retaining the ability to have a complex permission system.

In general, it's worth remembering that authentication/identity and storing permissions are not necessarily the same problem.

Solution 5 - .Net

You may be interested in RPX for a free API to authenticate your users

http://blog.maartenballiauw.be/post/2009/07/27/Authenticating-users-with-RPXNow-(in-ASPNET-MVC).aspx

Try the ASP.Net MVC Membership Starter Kit for an administrative API

Screenshots

http://www.squaredroot.com/2009/08/07/mvcmembership-release-1-0/

Old locations changesets (historic)

http://mvcmembership.codeplex.com/SourceControl/list/changesets

New Location:

http://github.com/TroyGoode/MembershipStarterKit

Solution 6 - .Net

This is a forth approach. Using the web matrix security classes you can use simple membership provider which can use EF so users and roles can be part of your domain model but also part of the IPrincipal and IIdentity MVC helpers.

I have created an example Github project to see how this can be used with automated self registration and email signup / password reset and the like.

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
QuestionNeil BarnwellView Question on Stackoverflow
Solution 1 - .NetJim PetkusView Answer on Stackoverflow
Solution 2 - .NetTim ScottView Answer on Stackoverflow
Solution 3 - .NetjesusdarioView Answer on Stackoverflow
Solution 4 - .NetCraig StuntzView Answer on Stackoverflow
Solution 5 - .Netmakerofthings7View Answer on Stackoverflow
Solution 6 - .NetAlexCView Answer on Stackoverflow