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

Understanding and Implementing ASP.NET Custom Forms Authentication

, 21 Jun 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
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.

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

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

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.

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

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)

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

 
Questionyou are so great Pinmemberdinesh143it10-Oct-13 7:04 
GeneralMy vote of 5 Pinmembervsdaking7-May-13 2:32 
GeneralMy vote of 5 Pinmembermangal deep gupta22-Apr-13 22:14 
GeneralMy vote of 4 PinmemberMember 98593628-Apr-13 6:56 
QuestionLog out PinmemberGeethaThozhukat14-Mar-13 6:10 
GeneralMy vote of 5 Pinmemberasp_crazy_guy11-Mar-13 0:05 
QuestionThanks And Query !! PinmemberRabbil12-Feb-13 1:29 
AnswerRe: Thanks And Query !! PinmvpRahul Rajat Singh12-Feb-13 18:14 
GeneralRe: Thanks And Query !! PinmemberRabbil14-Feb-13 2:49 
GeneralRe: Thanks And Query !! PinmvpRahul Rajat Singh17-Feb-13 18:36 
GeneralRe: Thanks And Query !! PinmemberRabbil18-Feb-13 21:13 
GeneralMy vote of 5 PinmemberSavalia Manoj M4-Dec-12 17:43 
Generalthanks Pinmemberinsane_rat26-Nov-12 0:44 
GeneralMy vote of 5 Pinmembermkerrigan16-Nov-12 6:38 
QuestionApplying Url Rewriting for this Custom Forms Authentication Pinmemberkrishnathota14-Nov-12 23:00 
GeneralMy vote of 5 Pinmemberraisingstar31-Oct-12 10:26 
QuestionCustom Membership Provder PinmemberMember 464125231-Oct-12 2:53 
AnswerRe: Custom Membership Provder PinmemberRahul Rajat Singh31-Oct-12 3:15 
Question.net version? PinmemberMember 464125230-Oct-12 19:56 
AnswerRe: .net version? PinmemberRahul Rajat Singh30-Oct-12 21:48 
AnswerArticle of the Day on Microsoft's site PinmemberRahul Rajat Singh2-Oct-12 1:22 
GeneralNice work Pinmemberabhilash_n27-Sep-12 9:51 
AnswerRe: Nice work PinmemberRahul Rajat Singh27-Sep-12 17:13 
Questioncan we store authorization information in sql? Pinmembersaeedm1217-Aug-12 22:34 
GeneralMy vote of 5 PinmemberPrasad_Kulkarni11-Jul-12 19:29 
GeneralMy vote of 5 PinmentorMd. Marufuzzaman3-Jul-12 11:32 
Excellent
GeneralMy vote of 5 PinmemberCK Park3-Jul-12 1:35 
QuestionMy vote of 5 PinmemberMember 423686528-Jun-12 14:17 
GeneralQuestion Pinmemberstefano.serati27-Jun-12 6:57 
AnswerRe: Question PinmemberRahul Rajat Singh27-Jun-12 19:20 
QuestionGood article. PinmemberHiren solanki22-Jun-12 21:39 
GeneralMy vote of 5 Pinmemberparul7722-Jun-12 1:19 
GeneralMy vote Pinmemberonurag1921-Jun-12 21:47 

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 | Terms of Use | Mobile
Web03 | 2.8.141220.1 | Last Updated 21 Jun 2012
Article Copyright 2012 by Rahul Rajat Singh
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid