Click here to Skip to main content
15,861,366 members
Articles / Web Development / ASP.NET
Article

Understanding and Implementing ASP.NET Custom Forms Authentication

Rate me:
Please Sign up or sign in to vote.
4.87/5 (67 votes)
21 Jun 2012CPOL6 min read 323.9K   10K   114   52
Understanding and implementing ASP.NET custom Forms Authentication.

Introduction

This article talks about ASP.NET the custom Forms Authentication mechanism. We will see how we can use a custom database in unison with the ASP.NET Forms Authentication mechanism to provide the desired level of security to our applications.

Background

We have already seen how we can use ASP.NET Roles and Membership classes to provide authentication and authorization in our applications (refer this). Also, we know about the server controls provided by ASP.NET to facilitate easy implementation of Roles and Membership functionalities in our application.

These Roles and Membership classes come in very handy when we want to provide authentication and authorization in our applications. 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.

In all the above scenarios, we find the need to use ASP.NET Forms Authentication but according to our database schema and our custom written code to handle user management. This is where the beauty of ASP.NET is shown. ASP.NET provides us fully functional and robust functionality for authentication and authorization and yet it gives us the possibility of taking full control and having full customization of the functionalities (which is actually not limited to Authentication related stuff, pretty much all areas of ASP.NET does that).

So what we are trying to do can easily be achieved by having a custom Forms Authentication mechanism. Using a custom Forms Authentication mechanism we can have our own tables to manage users and yet use the existing form authentication mechanism using GenericPrincipal. Let us try to work out an example and see how this can be done.

Using the code

Let us start by getting our requirements clear. We will be implementing a website that has the following structure:

Custom forms authentication article

The top level default page and the login page can be accessed by anyone. The pages in other folders can only be accessed by users in respective roles. Just to understand it in a clear way, let us look at the web.config of the PlatinumUser folder.

XML
<configuration>
    <appSettings/>
    <connectionStrings/>
    <system.web>
		<authorization>
			<allow roles="platinum" />
			<deny users="*" />
		</authorization>
    </system.web>
</configuration>

And the database that we have for user management looks like this.

Custom forms authentication article

We have a user table and every user can have multiple Roles. We will be using this table to authenticate the users.

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 too.

Now before going ahead, let us look at the little algorithm that we will be following to implement custom forms authentication and achieve the desired functionality.

  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.

Note: Each point in this algorithm can be found emphasized in the explanation below.

We have the algorithm worked out. We will now look at how each step is done. Let us start with Configure the application to use Forms authentication. To do this we need to set the authentication mode in the web.config file.

XML
<authentication mode="Forms">
	<forms loginUrl="Login.aspx" name="MyCustomAuthentication" timeout="30"/>			
</authentication>

This web.config indicates that Forms Authentication will be used in this application. The default loginUrl is Login.aspx. The name of the authentication ticket cookie will be MyCustomAuthentication and if this cookie is created in non persistent mode, then the timeout period for that is 30 minutes.

Now we have our website set to use Forms authentication and we have also specified the default login page so we also took care of #3 of our algorithm, i.e., Whenever user tries to access the restricted area, push him to the Login page. The next thing is to Create a login page.

Custom forms authentication article

Once this login page is done, we need to have some mechanism so that When the user tries to login, verify his credentials using the database. To do this, I have created a small helper class. Ideally this logic will be placed in a Data Access layer or might be present in ORMs. But for the sake of simplicity I have created a small helper class do to this.

C#
public class DBHelper
{
    //Validate the user from DB
    public static bool CheckUser(string username, string password)
    {
        DataTable result = null;
        try
        {
            using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["userDbConnectionString"].ConnectionString))
            {
                using (SqlCommand cmd = con.CreateCommand())
                {
                    cmd.CommandType = CommandType.Text;
                    cmd.CommandText = "select password from Users where username = @uname";
                    cmd.Parameters.Add(new SqlParameter("@uname", username));

                    using (SqlDataAdapter da = new SqlDataAdapter(cmd))
                    {
                        result = new DataTable();
                        da.Fill(result);
                    }

                    if (password.Trim() == result.Rows[0]["password"].ToString().Trim())
                    {
                        //user id found and password is matched too so lets do soemthing now
                        return true;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            //Pokemon exception handling
        }

        //user id not found, lets treat him as a guest        
        return false;
    }

    //Get the Roles for this particular user
    public static string GetUserRoles(string username)
    {
        DataTable result = null;
        try
        {
            using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["userDbConnectionString"].ConnectionString))
            {
                using (SqlCommand cmd = con.CreateCommand())
                {
                    cmd.CommandType = CommandType.Text;
                    cmd.CommandText = "select roles from Users where username = @uname";
                    cmd.Parameters.Add(new SqlParameter("@uname", username));

                    using (SqlDataAdapter da = new SqlDataAdapter(cmd))
                    {
                        result = new DataTable();
                        da.Fill(result);
                    }

                    if(result.Rows.Count == 1)
                    {
                        return result.Rows[0]["roles"].ToString().Trim();
                    }
                }
            }
        }
        catch (Exception ex)
        {
            //Pokemon exception handling
        }

        //user id not found, lets treat him as a guest        
        return "guest";
    }
}

Now whenever the user tries to log in, If the login is successful, keep the username and his Roles in Session variable to use it further, and also we need to Create an authentication ticket for the user (an encypted cookie). So let us do all this in the Button click event of the login page.

C#
protected void Button1_Click(object sender, EventArgs e)
{
    string roles;
    string username = TextBox1.Text.Trim();
    if (DBHelper.CheckUser(username, TextBox2.Text.Trim()) == true)
    {
        //These session values are just for demo purpose to show the user details on master page
        Session["User"] = username;
        roles = DBHelper.GetUserRoles(username);
        Session["Roles"] = roles;

        //Let us now set the authentication cookie so that we can use that later.
        FormsAuthentication.SetAuthCookie(username, false);

        //Login successful lets put him to requested page
        string returnUrl = Request.QueryString["ReturnUrl"] as string;

        if (returnUrl != null)
        {
            Response.Redirect(returnUrl);
        }
        else
        {
            //no return URL specified so lets kick him to home page
            Response.Redirect("Default.aspx");
        }
    }
    else
    {
        Label1.Text = "Login Failed";
    }
}

Now before going further, 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 specific details in this principle. 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 [#7 and #8 of our algorithm are covered].

C#
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;

                //let us extract the roles from our own custom cookie
                string roles = DBHelper.GetUserRoles(username);

                //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
            }
        }
    }
}

Now when we run the application, we will see that the authorization mechanism is working as specified in the respective web.config files in perfect unison with our own custom database and authentication logic.

Custom forms authentication article

Note: Running the sample application, looking at code, and debugging the application is strongly recommended to get a full understanding of the above mentioned concepts.

Points of interest

When I came across the requirements of having custom forms authentication, I was rather intimidated. It was partly because I only knew how the ASP.NET default membership provider worked and partly because the hype about how difficult it is to do custom forms authentication. When I sat on it, I found it rather straightforward and easy.

There is one more major area that is also worth looking into to understand the authentication and authorization mechanism completely and that is implementing a custom Membership Provider. Perhaps I will do that in a separate article.

History

  • June 20. 2012: First version.

License

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


Written By
Architect
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[^]

  • Microsoft MVP 2015

Comments and Discussions

 
Questiontimeout in non persistent mode? Pin
tono_mex4-Mar-18 13:13
tono_mex4-Mar-18 13:13 
QuestionForms Authentication deadlock Pin
alexvw30-May-17 5:39
alexvw30-May-17 5:39 
Questionencrypted cookie Pin
Member 888171422-Jul-15 20:51
Member 888171422-Jul-15 20:51 
AnswerRe: encrypted cookie Pin
JavaDummie22-Dec-15 20:00
JavaDummie22-Dec-15 20:00 
C#
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
    1, // Ticket version
    "your-username", // Username associated with ticket
    DateTime.Now, // Date/time issued
    DateTime.Now.AddMinutes(30), // Date/time to expire
    true, // for a persistent user cookie
    "Your-user-role", // User-data, in this case the roles
    FormsAuthentication.FormsCookiePath);

// Cookie Encryption
String hash = FormsAuthentication.Encrypt(ticket);

The encrypted string is now in the hash VARIABLE ;-)

QuestionPrincipal.GenericIdentity Pin
Member 888171422-Jul-15 20:04
Member 888171422-Jul-15 20:04 
Questiondynamic forms and roles Pin
Member 888171422-Jul-15 20:01
Member 888171422-Jul-15 20:01 
Questiondatabase Pin
Member 117392555-Jun-15 22:48
Member 117392555-Jun-15 22:48 
AnswerRe: database Pin
Rahul Rajat Singh10-Jun-15 18:15
professionalRahul Rajat Singh10-Jun-15 18:15 
QuestionOne request regarding sql membership Pin
Tridip Bhattacharjee31-Mar-15 21:28
professionalTridip Bhattacharjee31-Mar-15 21:28 
QuestionQuestion Pin
Member 112007939-Nov-14 20:33
Member 112007939-Nov-14 20:33 
QuestionVery good description Pin
ankursoft31-Oct-14 2:09
ankursoft31-Oct-14 2:09 
QuestionThank you! Pin
Member 1111232026-Sep-14 9:29
Member 1111232026-Sep-14 9:29 
GeneralMy vote of 5 Pin
Foosfam13-Sep-14 7:00
Foosfam13-Sep-14 7:00 
QuestionCode works perfect Pin
Member 1072690526-Apr-14 16:02
Member 1072690526-Apr-14 16:02 
QuestionI could not do the example. Pin
Denilson Ribeiro16-Apr-14 16:04
Denilson Ribeiro16-Apr-14 16:04 
QuestionProblem with code Pin
Denilson Ribeiro15-Apr-14 3:09
Denilson Ribeiro15-Apr-14 3:09 
GeneralMy vote of 4 Pin
Pratik Bhuva24-Mar-14 3:48
professionalPratik Bhuva24-Mar-14 3:48 
GeneralMy vote of 5 Pin
walterhevedeich7-Jan-14 15:31
professionalwalterhevedeich7-Jan-14 15:31 
GeneralMy vote of 4 Pin
dennisigah16-Nov-13 18:54
professionaldennisigah16-Nov-13 18:54 
Questionyou are so great Pin
dinesh143it10-Oct-13 6:04
dinesh143it10-Oct-13 6:04 
GeneralMy vote of 5 Pin
vsdaking7-May-13 1:32
vsdaking7-May-13 1:32 
GeneralMy vote of 5 Pin
Mangal Deep Gupta22-Apr-13 21:14
Mangal Deep Gupta22-Apr-13 21:14 
GeneralMy vote of 4 Pin
Member 98593628-Apr-13 5:56
Member 98593628-Apr-13 5:56 
QuestionLog out Pin
GeethaThozhukat14-Mar-13 5:10
GeethaThozhukat14-Mar-13 5:10 
GeneralMy vote of 5 Pin
zeego10-Mar-13 23:05
zeego10-Mar-13 23:05 

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

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