Click here to Skip to main content
Click here to Skip to main content

A Beginner's Tutorial on Custom Forms Authentication in ASP.NET MVC Application

, 24 Apr 2014
Rate this:
Please Sign up or sign in to vote.
In this we will discuss about implementing custom forms authentication in an ASP.NET MVC application.

Introduction

In this we will discuss about the ASP.NET Roles and Membership API from MVC perspective. We will try to see how the default Roles and Membership provides can be used for authentication and authorization in an MVC application. We will also see how we can implement custom forms authentication in an ASP.NET MVC application.

Background

When we are working on application in which authentication and authorization is a key requirement, we will find the ASP.NET roles and membership feature very useful. The basic principle and mechanism for forms authentication in ASP.NET MVC is same as of that of ASP.NET Webforms. But since we don't have the server controls with us the way to use it will be a little different from that of the webforms. Nevertheless, since the core principle behind the Forms authentication is same, I suggest following articles will provide a quick recap on ASP.NET Forms authentication and how it can be implemented and customized in WebForms applications.

Authentication and Authorization

Authentication means validating users. In this step, we verify user credentials to check whether the person tying to log in is the right one or not. Authorization on the other hand is keeping track of what the current user is allowed to see and what should be hidden from him. It is more like keeping a register to what to show and what not to show to the user.

Whenever a user logs in, he will have to authenticate himself with his credentials. Once he is authenticated, he will be authorized to see resources/pages of the website. Mostly these two concepts go together.

Type of Authentications

Before moving ahead, let us first see the two main type of authentications that are used mostly in ASP.NET applications.

  1. Windows authentication: In this mode, the users are authenticated on their Windows username and password. This method is least recommended in an internet scenario. In an internet scenario, we should always use "Forms based authentication".
  2. Forms based authentication: In this type of authentication, the user will explicitly have to provide his credentials and these credentials, once verified by the server, will let the user to log in.

We will be discussing the form authentication in details in the rest of the article.

Using the code

The default ASP.NET Roles and Membership classes come in very handy when we want to provide authentication and authorization in our applications. Using the default membership API will use the membership database and provide us will all the functionality required to manage the user roles and membership.

ASP.NET also provides a way to implement custom Roles and Membership to take more granular control over things. We might still find ourselves in situations where we need to have our own database for tracking users and their roles. The reasons could be:

  • We have an existing database and we are trying to implement an application using that.
  • The Roles and Membership functionality is overkill for our application.
  • The Roles and Membership functionality is not sufficient for our applications and we need custom data.

So let us first discuss the default membership API and what visual studio provides us out of the box. We will then move on to taking the full control on authorization and authentication on our hand by implementing custom forms authentication.

Forms Authentication

To enable forms authentication we need to perform following steps in our application.

  1. Configure the application to use Forms Authentication.
  2. Create a login page.
  3. Whenever a user tries to access the restricted area, push him to the Login page.
  4. When the user tries to login, verify his credentials using the database.
  5. If the login is successful, keep the username and his Roles in a Session variable to use it further.
  6. Create an authentication ticket for the user (an encrypted cookie). We can have this persistent or non persistent.
  7. Facilitate the User/Roles extraction using the authentication ticket.
  8. Use the user/Roles found in the last step and create a Principal using that so that the ASP.NET Forms Authentication mechanism can use this data.

Now we will see how the default membership API does all these things for us and how we can implement all these steps ourselves.

Default Membership API

When we create an MVC internet application. The visual studio project wizard does all these above mentioned steps for us. it will create a Membership database in the App_data directory of our application (actually the location depends on the connectionstring specified in web.config file).

It will generate the Controller ode that will check the database for user authentication, create the authentication cookie and creation of Principal based on the roles configured in our database. It will also generate all the view required for authentication. The image below shows the generated Controller, Views and the database.


The Roles and user can be configured either from code or from the Web Site Administration Tool(WSAT) in the same manner as of that in WebForms application. So let us create a role called "admin" and 2 users "admin" and "user" using WSAT. The "admin" will be in "admin" role and the "user" will not be in any role.


Now from the applications perspective, we only need to mark the views that need authentication and authorization and the default membership classes and the generated classed will take care of performing the authentication and authorization.

Let us say that we want only authentication users to be able to view the Home/Index page. And only the users in "Admin" role can access the Home/About page. To do this we need to decorate the respective action in controller with the authorize attribute as:

public class HomeController : Controller
{
    [Authorize]
    public ActionResult Index()
    {
        ViewBag.Message = "Welcome to ASP.NET MVC!";

        return View();
    }

    [Authorize(Roles="Admin")]
    public ActionResult About()
    {
        return View();
    }
}

And that is all that is required to require authentication and authorization if we are using default Membership API and the visual studio generated code for authentication and authorization. We can do some level of customization in the AccountController class if we need some added functionality.

Note: The article does not contain a sample for the default membership usage because it is just the matter of creating a new MVC 3 internet application and all the code will be generated by visual studio itself. It is highly recommended to look at the AccountController class to see how it is performing various operations.

Custom Forms Authentication

Now if we don't want to use the default membership API and the visual studio generated code then we can choose to implement our own authentication and authorization mechanism. To do this we will have take care of implementing all the steps required for forms authentication that we discussed earlier in the article. So let us create and empty MVC 3 application and see how we can implement custom forms authentication.

Configuring Forms Authentication

Now the first thing that we need to do is to configure the application to use the Forms authentication. This can be done in the web.config file.

<authentication mode="Forms">
  <forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>

Preparing the user Database

let us now create a small database that we will use to perform authentication. This database contains only two users like our old earlier example. "admin" and "user". "admin" is in "admin" role and "user" is not in any role.


Note: The database is neither optimized nor normalized as that was not the main intent of this article. A real world example of database will be more optimized and perhaps more complex. The passwords will not be in clear text for sure, They will either be encrypted or "hashed & salted".

Now to perform data access let us use entity framework so that we don't have to write all the boilerplate code required to create our model and data access logic. The generated entity for our database will look like:


Creating the Controllers and Views

let us now go ahead and create a controller that will take care of the authentication logic. We will create the functionality for Login and Logout but other functionality are user creation and password change can be easily implemented on same lines(its the matter of validating the user Model and performing CRUD operations on the table after encryption or hashing and salting).

Here is our controller with login and logout action:

public ActionResult Login()
{
    return View();
}

[HttpPost]
public ActionResult Login(User model, string returnUrl)
{
    // Lets first check if the Model is valid or not
    if (ModelState.IsValid)
    {
        using (userDbEntities entities = new userDbEntities())
        {
            string username = model.username;
            string password = model.password;

            // Now if our password was enctypted or hashed we would have done the
            // same operation on the user entered password here, But for now
            // since the password is in plain text lets just authenticate directly

            bool userValid = entities.Users.Any(user => user.username == username && user.password == password);

            // User found in the database
            if (userValid)
            {

                FormsAuthentication.SetAuthCookie(username, false);
                if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
                    && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
                {
                    return Redirect(returnUrl);
                }
                else
                {
                    return RedirectToAction("Index", "Home");
                }
            }
            else
            {
                ModelState.AddModelError("", "The user name or password provided is incorrect.");
            }
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

public ActionResult LogOff()
{
    FormsAuthentication.SignOut();

    return RedirectToAction("Index", "Home");
}

Now in the above code when the user tries to login we will check if the user with the given user credentials exist in the user database or not. If it exist we set the authentication ticket and move forward. Now if our password was encrypted or hashed we would have done the same operation on the user entered password before checking in database, but since the password is in plain text lets just authenticate directly.

Now before moving ahead let us look at the view that will take the user credentials and perform the authentication.


Let us now create a simple Controller which will contain two actions. One action that can be performed by any authenticated user and other that can be performed by the users in "admin" role only.

public class HomeController : Controller
{
    [Authorize]
    public ActionResult Index()
    {
        ViewBag.Message = "This can be viewed only by authenticated users only";
        return View();
    }

    [Authorize(Roles="admin")]
    public ActionResult AdminIndex()
    {
        ViewBag.Message = "This can be viewed only by users in Admin role only";
        return View();
    }
}

Now when we run the application, we can see that the users will be asked to enter their credentials when they try to access the Home controller's views. Upon successful login they will be able to see the Index page.


But if we try to look at the AdminIndex page we will not be able to see that. The reason for that is that currently the users roles are not being used.

Facilitating Roles extraction using the authentication ticket

Now to use the roles specified in our database, there is one thing to understand. When Forms authentication is being used, whenever the need for authentication arises, the ASP.NET framework checks with the current IPrinciple type object. The user ID and Role contained in this IPrinciple type object will determine whether the user is allowed access or not.

So far we have not written code to push our user's Role details in this principle object. To do that we need to override a method called FormsAuthentication_OnAuthenticate in global.asax. This method is called each time ASP.NET framework tries to check authentication and authorization with respect to the current Principle.

What we need to do now is to override this method. Check for the authentication ticket (since the user has already been validated and the ticket was created) and then supply this User/Role information in the IPrinciple type object. We can implement our custom Principle type too but to keep it simple, we will simply create a GenericPriciple object and set our user specific details into it

protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs e)
{
    if (FormsAuthentication.CookiesSupported == true)
    {
        if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
        {
            try
            {
                //let us take out the username now                
                string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
                string roles = string.Empty;

                using (userDbEntities entities = new userDbEntities())
                {
                    User user = entities.Users.SingleOrDefault(u => u.username == username);

                    roles = user.Roles;
                }
                //let us extract the roles from our own custom cookie
                

                //Let us set the Pricipal with our user specific details
                e.User = new System.Security.Principal.GenericPrincipal(
                  new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(';'));
            }
            catch (Exception)
            {
                //somehting went wrong
            }
        }
    }
}

Note: In MVC 4 and later versions, this event will not work. To make the custom forms authentication work in MVC 4 and later versions, we need to put this code in Application_PostAuthenticateRequest event.

protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
    if (FormsAuthentication.CookiesSupported == true)
    {
        if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
        {
            try
            {
                //let us take out the username now                
                string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
                string roles = string.Empty;

                using (userDbEntities entities = new userDbEntities())
                {
                    User user = entities.Users.SingleOrDefault(u => u.username == username);

                    roles = user.Roles;
                }
                //let us extract the roles from our own custom cookie
                

                //Let us set the Pricipal with our user specific details
                HttpContext.Current.User  = new System.Security.Principal.GenericPrincipal(
                  new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(';'));
            }
            catch (Exception)
            {
                //somehting went wrong
            }
        }
    }
} 

And now when we run our application we can even access the AdminIndex page. Now we have all the steps required to implement the custom forms authentication in place. We have successfully implemented custom forms authentication in an ASP.NET MVC application.

Note: This article contains code snippets that are only for demonstration of concepts of the article and it does not follow any best practices Only the logic should be taken from this article as the code in the article is not of production quality.

Point of interest

In this article we have tried to look into ASP.NET roles and membership API. We saw how MVC project comes with the default Membership API implementation and some default Controllers and view which can be utilized to easily integrate forms authentication in an MVC application. We also saw how we can take full control over the forms authentication and implement custom forms authentication. This article has been written from a beginner's perspective. hope this has been informative.

History

  • 16 April 2013: First version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Rahul Rajat Singh
Software Developer (Senior)
India India
I Started my Programming career with C++. Later got a chance to develop Windows Form applications using C#. Currently using C#, ASP.NET & ASP.NET MVC to create Information Systems, e-commerce/e-governance Portals and Data driven websites.

My interests involves Programming, Website development and Learning/Teaching subjects related to Computer Science/Information Systems. IMO, C# is the best programming language and I love working with C# and other Microsoft Technologies.
  • Microsoft Certified Technology Specialist (MCTS): Web Applications Development with Microsoft .NET Framework 4
  • Microsoft Certified Technology Specialist (MCTS): Accessing Data with Microsoft .NET Framework 4
  • Microsoft Certified Technology Specialist (MCTS): Windows Communication Foundation Development with Microsoft .NET Framework 4
 
If you like my articles, please visit my website for more: www.rahulrajatsingh.com[^]
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
SuggestionAwesome thanks! Pinmemberplayerdan10-Aug-14 18:11 
Questionthanks.. PinmemberMember 107325629-Jun-14 21:32 
GeneralMy vote of 5 PinmemberHumayun Kabir Mamun30-Apr-14 20:33 
Questiongreat article! PinmemberMetalPower14-Mar-14 6:16 
Questionthanks Pinmemberdananjayadod6-Mar-14 16:08 
QuestionMy vote of 5! PinmemberVladyslav Fedoniuk11-Dec-13 10:35 
QuestionThe referenced component 'EntityFramework' could not be found. MvcFormsAuthenticationSample Pinmembernataliaps19-Nov-13 3:57 
GeneralMy vote of 5 PinprofessionalPratik Bhuva18-Nov-13 3:41 
QuestionProblem with timetou PinmemberCesar R Bagatoli25-Oct-13 6:44 
AnswerRe: Problem with timetou PinmemberCesar R Bagatoli25-Oct-13 9:34 
QuestionNot able to authorize admin role PinmemberGagan Bhardwaj22-Oct-13 2:57 
AnswerRe: Not able to authorize admin role PinmemberGagan Bhardwaj22-Oct-13 3:14 
GeneralRe: Not able to authorize admin role PinmvpRahul Rajat Singh24-Apr-14 2:22 
Questiontanx Pinmemberali.rasoulzadeh13-Oct-13 19:55 
QuestionTried this but have a problem Pinmembersttaq9-Oct-13 6:16 
QuestionPay load PinmemberAlexandru Mărculescu26-Jul-13 1:13 
QuestionFormsAuthentication_OnAuthenticate PinmemberMikael Jirhage22-Jul-13 3:20 
Nice article.
 
Just one comment on FormsAuthentication_OnAuthenticate. You are not overriding this method. You are handling the Authenticate event with this method.
AnswerArticle of the Day on Microsoft's site PinmvpRahul Rajat Singh21-Jul-13 19:10 
BugLogin works for multiple projects. PinmemberManan Vaghasiya19-Jul-13 3:17 
AnswerRe: Login works for multiple projects. PinmvpRahul Rajat Singh21-Jul-13 17:25 
GeneralRe: Login works for multiple projects. PinprofessionalManan Vaghasiya21-Jul-13 19:56 
GeneralThank You Pinmemberagan2414-May-13 17:39 
QuestionFormsAuthentication_OnAuthenticate in Global.asax Pinmemberpeterfoo8-May-13 9:00 
GeneralLittle issue with Roles Pinmemberlogistum29-Apr-13 10:18 
GeneralRe: Little issue with Roles PinmemberFabio Franco4-Jun-13 3:40 
QuestionWiil you be doing this for SimpleMembership with MVC4? PinmemberCraig Simon22-Apr-13 10:34 
GeneralMy vote of 5 Pinmembermruvinskiy16-Apr-13 10:08 
GeneralMy vote of 5 PinmemberPrasad Khandekar16-Apr-13 0:05 
GeneralRe: My vote of 5 PinmvpRahul Rajat Singh16-Apr-13 0:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140821.2 | Last Updated 24 Apr 2014
Article Copyright 2013 by Rahul Rajat Singh
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid