Home  |  FAQ  |  About  |  Contact  |  View Source   
 
SEARCH:
 
BROWSE:
    My Hood
Edit My Info
View Events
Read Tutorials
Training Modules
View Presentations
Download Tools
Scan News
Get Jobs
Message Forums
School Forums
Member Directory
   
CONTRIBUTE:
    Sign me up!
Post an Event
Submit Tutorials
Upload Tools
Link News
Post Jobs
   
   
Home >  Tutorials >  General ASP.NET >  Role-based Security with Forms Authentication
Add to MyHood
   Role-based Security with Forms Authentication   [ printer friendly ]
Stats
  Rating: 4.75 out of 5 by 12 users
  Submitted: 04/28/02
Heath Stewart ()

 
Forms Authentication in ASP.NET can be a powerful feature. With very little code and effort, you can have a simple authentication system that is platform-agnostic. If your needs are more complex, however, and require more efficient controls over assets, you need the flexibility of groups. Windows Authentication gives you this flexibility, but it is not compatible with anything but Internet Explorer since it uses NTLM, Microsoft's proprietary authentication system. Now you must choose how to manage your assets: provide multiple login pages / areas and force users to register for each, or assign groups to users and limit access to pages / areas to particular groups. Obviously, you must choose the latter.

Role-based security in Forms Authentication is one thing Microsoft left out in this round for .NET, but they didn't leave you high-and-dry. The mechanisms are there, they're just not intuitive to code. This tutorial will cover the basics of Forms Authentication, how to adapt it to make use of role-based security, and how to implement role-based security on your site with single sign-ons.


Introduction

This tutorial is all about role-based security with Forms Authentication, a detail that Microsoft left out of .NET for this round. If you're looking for a simple way to secure an area of your web server, I suggest you read Andrew Ma's tutorial entitled, Forms Authentication Tutorial. This tutorial will use different techniques that area almost completely incompatible with the standard Forms Authentication, save the setup, which we'll cover shortly.

To follow along in this tutorial, you'll need to create a database, a web application, several secured directories, and a few ASP.NET Web Forms (pages).


Creating the Databases

We will create a simple database containing a flat table for this tutorial. Using the <credentials/> section of the Web.config file is not an option because no mechanism for roles is supported. For the purposes of brevity, the table we create will be very simple. You're welcome to expand the database to make use of relations (what I would do and actual do use on several sites) for roles. The implementation does start to get a little messy depending on how you do it, and the details are left up to you. This is merely a tutorial about developing role-based security.

So, choose what database management system you want to use (DBMS). For this tutorial, we'll choose the Microsoft Data Engine (MSDE) available with Visual Studio .NET, Office XP Developer, and several other products. We'll add one database, say "web", and then add one table, say "users". To the "users" table, we'll add three fields: "username", "password", and "roles". Set the "username" field to the primary key (since it'll be used for look-ups and needs to be unique), and optionally create an index on the "username" and "password" fields together. If you're using Table-creation SQL Scripts, your script might look something like this:

CREATE DATABASE web

CREATE TABLE users
(
 username nvarchar(64) CONSTRAINT users_PK PRIMARY KEY,
 password nvarchar(128),
 roles nvarchar(64)
)

CREATE INDEX credentials ON users
(
 username,
 password
)

Feel free to add some credentials to your database, picking a few roles you think are good group names for your site, such as "Administrator", "Manager", and "User". For this tutorial, put them in comma-delimited format in the "roles" field like the following, pipe-delimited (|) table:

username|password|roles
"hstewart"|"devhood"|"Administrator,User"
"joe"|"schmoe"|"User"

Take note to make the roles case-sensitive. Now let's move on to creating our pages necessary for role-based Forms Authentication.


Creating the Login Pages

If you haven't already done so, create a new Web Application, or attach to an existing Web Application, such as your web server's document root, "/". For this tutorial, we'll assume the Web Application resides in "/", though the procedure for any Web Application is the same.

Before we create any pages or setup our Web.config file, you must understand one thing: the "login.aspx" (or whatever you call your login page) must be public. If it isn't, your users will not be able to log-in, and could be stuck in an infinite loop of redirects, though I've not tested this and don't care to. So, this tutorial will assume that "login.aspx" is in "/", while we have two secured sub-directories, "users" and "administrators".

First, we must create a Forms Authentication login system that supports roles. Because Microsoft did not provide for this easily, we will have to take over the process of creating the authentication ticket ourselves! Don't worry, it's not as hard as it sounds. A few pieces of information are needed, and the cookie has to be stored under the right name - the name matching the configured name for Forms Authentication in your root Web.config file. If these names don't match, ASP.NET won't find the Authentication Ticket for the Web Application and will force a redirect to the login page. For simplicity, we will put the code directly into the ASP.NET Web Form, which is easier to code for DevHood and should look something like the following:


  <%@ Page Language="C#" %>
  <%@ Import Namespace="System.Data" %>
  <%@ Import Namespace="System.Data.SqlClient" %>
<html>  <head>   <title>Login</title>  </head>  <script runat="server">  // If you're using code-behind, make sure you change "private" to  // "protected" since the .aspx page inherits from the .aspx.cs  // file's class  private void btnLogin_Click(Object sender, EventArgs e)  {   // Initialize FormsAuthentication, for what it's worth   FormsAuthentication.Initialize();      // Create our connection and command objects   SqlConnection conn =    new SqlConnection("Data Source=localhost;Initial Catalog=web;");   SqlCommand cmd = conn.CreateCommand();   cmd.CommandText = "SELECT roles FROM web WHERE username=@username " +    "AND password=@password";     // Fill our parameters   cmd.Parameters.Add("@username", SqlDbType.NVarChar, 64).Value = Username;   cmd.Parameters.Add("@password", SqlDbType.NVarChar, 128).Value =    FormsAuthentication.HashPasswordForStoringInConfigFile(Password);     // Execute the command   SqlDataReader reader = cmd.ExecuteReader();   if (reader.Read())   {    // Create a new ticket used for authentication    FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(     1, // Ticket version     Username, // Username associated with ticket     DateTime.Now, // Date/time issued     DateTime.Now.AddMinutes(30), // Date/time to expire     true, // "true" for a persistent user cookie     reader.GetString(0), // User-data, in this case the roles     FormsAuthentication.FormsCookiePath); // Path cookie valid for      // Hash the cookie for transport    string hash = FormsAuthentication.Encrypt(ticket);    HttpCookie cookie = new HttpCookie(     FormsAuthentication.FormsCookieName, // Name of auth cookie     hash); // Hashed ticket      // Add the cookie to the list for outgoing response    Response.Cookies.Add(cookie);      // Redirect to requested URL, or homepage if no previous page requested    string returnUrl = Request.QueryString["ReturnUrl"];    if (returnUrl == null) returnUrl = "/";        // Don't call FormsAuthentication.RedirectFromLoginPage since it could    // replace the authentication ticket (cookie) we just added    Response.Redirect(returnUrl);   }   else   {    // Never tell the user if just the username is password is incorrect.    // That just gives them a place to start, once they've found one or    // the other is correct!    ErrorLabel = "Username / password incorrect. Please try again.";    ErrorLabel.Visible = true;   }  }  </script>  <body>   <p>Username: <input id="Username" runat="server" type="text"/><br />   Password: <input id="Password" runat="server" type="password"/><br />   <asp:Button id="btnLogin" runat="server" OnClick="btnLogin_Click"    Text="Login"/>   <asp:Label id="ErrorLabel" runat="Server" ForeColor="Red"    Visible="false"/></p>  </body> </html>

You'll notice above that we do one other thing with our passwords: we hash them. Hashing is a one-way algorithm that makes a unique array of characters. Even changing one letter from upper-case to lower-case in your password would generate a completely different hash. We'll store the passwords in the database as hashes, too, since this is safer. In a production environment, you'd also want to consider having a question and response challenge that a user could use to reset the password. Since a hash is one-way, you won't be able to retrieve the password. If a site is able to give your old password to you, I'd consider steering clear of them unless you were prompted for a client SSL certificate along the way for encrypting your passphrase and decrypting it for later use, though it should still be hashed.

If you don't want to use hashes during this tutorial, change the line that reads "FormsAuthentication.HAshPasswordForStoringInConfigFile(Password)" to just "Password". NOTE: this will send your password across the wire in clear text.

Next, we'll need to modify the Global.asax file. If your Web Application doesn't have one already, right-click on the Web Application, select "Add->Add New Item...->Global Application Class". In either the "Global.asax" or "Global.asax.cs" (or "Global.asax.vb", if you're using VB.NET), find the event handler called "Application_AuthenticateRequest". Make sure it imports / uses the "System.Security.Principal" namespace and modify it like so:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
 if (HttpContext.Current.User != null)
 {
  if (HttpContext.Current.User.Identity.IsAuthenticated)
  {
   if (HttpContext.Current.User.Identity is FormsIdentity)
   {
    FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
    FormsAuthenticationTicket ticket = id.Ticket;

    // Get the stored user-data, in this case, our roles
    string userData = ticket.UserData;
    string[] roles = userData.Split(',');
    HttpContext.Current.User = new GenericPrincipal(id, roles);
   }
  }
 }
}

What's happening above is that since our principal (credentials - which are your username and password - and memberships / roles) is not stored as part of our cookie (nor should it, since a user could modify their list of role-memberships), so it needs to be generated for each request. If you search long and hard enough on Microsoft web site, you'll find this documentation buried. Once the Principal is created, we add it to the current context for the user, which the receiving page can use to retrieve credentials and role-memberships.


Securing Directories with Role-based Forms Authentication

To make the role-based authentication work for Forms Authentication, make sure you have a Web.config file in your Web Application root. For the authentication setup, this particular Web.config file must be in your Web Application's document root. You can override the <authorization/> in Web.config files for sub-directories.

To begin, make sure your Web.config file has at least the following:

<configuration>
 <system.web>
  <authentication mode="Forms">
   <forms name="MYWEBAPP.ASPXAUTH"
    loginUrl="login.aspx"
    protection="All"
    path="/"/>
  </authentication>
  <authorization>
   <allow users="*"/>
  </authorization>
 </system.web>
</configuration>

The FormsAuthentication name ("MYWEBAPP.ASPXAUTH") above it arbitrary, although the name there and the name in the HttpCookie we created to hold the hashed FormsAuthenticationTicket must match, for even though we are overriding the ticket creation, ASP.NET still handles the authorization automatically from the Web.config file.

To control authorization (access by a particular user or group), we can either 1) add some more elements to the Web.config file from above, or 2) create a separate Web.config file in the directory to be secure. While, I prefer the second, I will show the first method:

<configuration>
 <system.web>
  <authentication mode="Forms">
   <forms name="MYWEBAPP.ASPXAUTH"
    loginUrl="login.aspx"
    protection="All"
    path="/"/>
  </authentication>
  <authorization>
   <allow users="*"/>
  </authorization>
 </system.web>
 <location path="administrators">
  <system.web>
   <authorization>
    <!-- Order and case are important below -->
    <allow roles="Administrator"/>
    <deny users="*"/>
   </authorization>
  </system.web>
 </location>
 <location path="users">
  <system.web>
   <authorization>
    <!-- Order and case are important below -->
    <allow roles="User"/>
    <deny users="*"/>
   </authorization>
  </system.web>
 </location>
</configuration>

The Web Application always creates relative paths from the paths entered here (even for "login.aspx"), using it's root directory as the starting point. To avoid confusion with that condition and to make directories more modular (being able to move them around without changing a bunch of files), I choose to put a separate Web.config file in each secure sub-directory, which is simply the <authorization/> section like so:

<configuration>
 <system.web>
  <authorization>
   <!-- Order and case are important below -->
   <allow roles="Administrator"/>
   <deny users="*"/>
  </authorization>
 </system.web>
</configuration>

Notice, too, that the role(s) is(are) case-sensitive. If you want to allow or deny access to more than one role, delimit them by commas.

That's it! Your site is setup for role-based security. If you use code-behind, compile your application first. Then try to access a secure directory, such as "/administrators", and you'll get redirected to the login page. If login was successful, you're in, unless your role prohibits it, such as the "/administrators" area. This is hard for the "login.aspx" page to determine, so I'd recommend a Session variable to store the login attempts and after so many times, return an explicit "Denied" statement. There is another way, however, which is discussed below.


Conditionally Showing Controls with Role-based Forms Authentication

Sometimes it's better to show / hide content based on roles when you don't want to duplicate a bunch of pages for various roles (user groups). Such examples would be a portal site, where free- and membership-based accounts exist and membership-based accounts can access premium content. Another example would be a news page that would display an "Add" button for adding news links if the current user is in the "Administrator" role. This section describes how write for such scenarios.

The IPrincipal interface, which the GenericPrincipal class we used above implements, has a method called "IsInRole()", which takes a string designating the role to check for. So, if we only want to display content if the currently logged-on user is in the "Administrator" role, our page would look something like this:

<html>
 <head>
  <title>Welcome</title>
  <script runat="server">
  protected void Page_Load(Object sender, EventArgs e)
  {
   if (User.IsInRole("Administrator"))
    AdminLink.Visible = true;
  }
  </script>
 </head>
 <body>
  <h2>Welcome</h2>
  <p>Welcome, anonymous user, to our web site.</p>
  <asp:HyperLink id="AdminLink" runat="server"
   Text="Administrators, click here." NavigateUrl="administrators/"/>
 </body>
</html>

Now the link to the Administrators area of the web site will only show up if the current user is logged in and is in the "Administrator" role. If this is a public page, you should provide a link to the login page, optionally setting the QueryString variable called "ReturnUrl" to the path on the server you want the user to return to upon successful authentication.


Summary

This tutorial was created to help you understand the important of role-based security, as well as implement role-based security on your web site with ASP.NET. It's not a hard mechanism to implement, but it does require some know-how of what principals are, how credentials are authenticated, and how users / roles are authorized. I hope you have found this tutorial helpful and interesting, and that it leads you to implement role-based Forms Authentication on your current or upcoming site!

Return to Browsing Tutorials

Email this Tutorial to a Friend

Rate this Content:  
low quality  1 2 3 4 5  high quality

Reader's Comments Post a Comment
 
That was a great tutorial. I will be using this real soon and it will be a lot easier now that you have explained how it is done. Definately a 5!
-- Bryant Likes, April 29, 2002
 
Great Tutorial
-- Kirk Boone, April 29, 2002
 
this is a very good tutorial...but i'm sorry, you really didn't explain what role based security is...what is role based security? i'm not really sure what the term means. thanks for explaining the term
-- J J, April 30, 2002
 
Roles are groups, like "Administrator", "User", "Manager", "Developer", etc. I thought this would've been apparent with context clues.
-- Heath Stewart, April 30, 2002
 
ah ok...now i understand...a good thing to remember...when writing a tutorial, or trying to teach something...teach/write at a lower level, just in case there is someone who understands part of what you are saying but not all. assuming something based on context clues is not always a good way to get a point across. you should come out and just say it, so there is nothing lost anywhere. but over all very good tutorial...thank you for explaining what a role is.
-- J J, April 30, 2002
 
No, I don't think you should spend that much time defining every word you use. Then a tutorial would be long and it would be harder read and especially frustrating for people who already know the basic definitions.
People are more likely to look up your tutorial when they are trying to figure out that topic rather than looking at it randomly not knowing what it is.
-- Andrew Ma, April 30, 2002
 
In your sample web.config, you're missing your closing </authentication> tag. It should go right before the <authorization> tag.
-- Andrew Ma, April 30, 2002
 
i never once suggested defining every word...but it would be helpful to give a brief intro on what you are talking about... ie what a role is and what it is used for...that would just make it a little easier, especially if you are looking for something like this but not sure if what you are looking is what you were actually looking for. say i wanted to do authentification just like this tutorial outlines, but wasn't sure what it was called, or knew it by a different name, at first glance of this tutorial i would not be able to tell that this is the tutorial i wanted without the tutorial telling what it is by defining a key word. i for one am not going to read through a whole tutorial just to find out if that's what i was looking for. you should be able to grab a person's attention in the first few lines of the tutorial. this is what they teach you in your freshman composition classes. grab the readers attention and then keep it. by describing what a role is then you will have my attention, and maybe then i will know if this is what i was looking for or not.
-- J J, April 30, 2002
 
Wow, a very complete tutorial. I would like to implement something similiar to this on my site. Thanks for the work!!!
-- Brad Rider, May 11, 2002
 
Very impressive tutorial, thanks a lot for posting this. These are the kinds of tutorials that make Devhood a good location for a lot of information.
-- Jon Wright, June 03, 2002
 
Very impressive tutorial, thanks a lot for posting this. These are the kinds of tutorials that make Devhood a good location for a lot of information.
-- Jon Wright, June 03, 2002
 
Excellent tutorial, it had almost everything I needed. What I need to do is pass a value[userID] that was returned from my web service(this takes the place of the SQL part), so that I can load there profile.

Any Ideas?

Again, excellant tutorial.

Knute
-- Michael Hestness, June 05, 2002
 
Michael, it doesn't matter how you get the roles, so long as you write them to UserData (or a Session or something) and read them the same way. You could even bypass that and get the values from a web service such as you're doing, but this would be a very slow and CPU-intensive process. Remember, this code is run with every request to a page! I would not recommend pulling them from a web service every time.

If you want to do it a similar way, call value[userID] once and assign the information to the UserData property. Just make sure that either value[userID] or whatever method returns information returns it in a way that you can put into a string and later parse in the same manner. In any event, you'll have cookie filled with the roles, so you may want to add some checking in there to make sure the user didn't change them (like a hash, which is intrinsic if you hash the cookie like you should anyway).
-- Heath Stewart, July 10, 2002
 
This was really useful, but i'm having real difficulty creating a "log out" button. can anyone help?
-- Jonathan Rowett, July 17, 2002
 
Several people have told me they're having problems with FormsAuthentication.SignOut(). I'm not sure why that is, since all it does is get the cookie name from the Web.config file and remove it from the collection. So, here's how to do it manually. It's untested (don't really have a lot of time to first implement Role-based FormsAuthentication on one of our sites right now just to test logging out and the original site where I developed this is out of my control now), but it should work:

1. Add an event handler for the Click event of a button or image or something.
2. In the body of the handler, do something like the following:

Request.Cookies.Remove(FormsAuthentication.FormsCookieName);
Response.Redirect("/", true);

3. You may want to try doing this from within the scope of the FormsAuthentication.FormsCookiePath value.

If that doesn't work, set the expiration time on the cookie, which is what most CGI scripts do:

HttpCookie cookie = Request.Cookies[FormsAuthentication.FormsCookieName];
cookie.Expires = DateTime.Now.AddDays(-1);
-- Heath Stewart, July 18, 2002
 
Hi, this tutorial is really cool, but when I try to Build the Visual Studio .NET project, show the following error:

Compiler Error Message: CS1501: No overload for method 'HashPasswordForStoringInConfigFile' takes '1' arguments

Can do you Help me with that?...

Best Regards.

Pablo Schuhwerk
-- Pablo Schuhwerk, August 26, 2002
 
That should be:

FormsAuthentication.HashPasswordForStoringInConfigFile(Password, "md5");
-- Heath Stewart, August 31, 2002
 
Good!
-- Joe Lee, September 03, 2002
 
This is an incredibly useful tutorial. I don't know why .NET doesn't have more support for role-based authentication. Almost every website I've made (that requires authentication) has used role-based auth.
-- Andrew Ma, September 05, 2002
 
Hi, great tutorial, but I'm having a slight problem. I separated your code in HTML and code behind, but now, when I try to compile it, I receive an error which says that FormsAuthentication... could not be found. Can anyone help me with this problem ?
-- Red tilldeath, September 12, 2002
 
I found it. But now, when I click the login-button, nothing happens ;) . He doesn 't use the script-code... Does anyone know how to resolve this problem ?
-- Red tilldeath, September 13, 2002
 
FormsAuthentication.SignOut() also clears out persistent cookies. Is there any setting to avoid this for persistent logon?

-- Geeta Chauhan, September 17, 2002
 
Heath Rocks! This is a great tutorial. I had some minor problems in implementing this solution, but everyone's configuration is going to be different.

This is by far the best and to the point tutorial on a subject I have encountered. Heath gets down to brass tacks and tells you what you need to to do to implement a solution.
-- Michael Hestness, October 15, 2002
 
I had successfully creating the login part, but I have a problem to sign user out. I tried the suggested soution i.e. set the cookies to expires:

HttpCookie cookie = Request.Cookies[FormsAuthentication.FormCookieName];
cookie.Expires = DateTime.NOw.AddDays(-1);
Response.Redirect("/someDirNeedAuth", true);

as well as follows:
FormsAuthentication.SignOut();
Response.Redirect("/someDirNeedAuth", true);

Both ways failed to sign user out. The browser displays the content of the page after the re-direction while a login page is expected.

Is there anyway that I could expired the authentication ticket instead?






-- Moh Yen Liew, October 22, 2002
 
Keep up-to-date on this article at .
-- Heath Stewart, February 02, 2003
 
Hi,

Great tutorial leaves me with just one question: Is is possible to do Role Validation through Forms Authentication _without_ using cookies?

I'm thinking the answer is probably 'no', as I can't think of anywhere else to store the current users roles apart from in the cookie.

any help appreciated!
thanks,
ski ski
-- ski ski, April 16, 2003
 
How about leaving your Global.asax as it is and put the code all together?


FormsIdentity id = new FormsIdentity(ticket);
HttpContext.Current.User = new GenericPrincipal(id, roles);
-- Daniel Fisher, June 23, 2003
 
Solution to Signout problem:

We should remove the cookie from the Response object not from the Request object. The following code works perfectly:

HttpCookie cookie = Response.Cookies[FormsAuthentication.FormsCookieName];
cookie.Expires = DateTime.Now.AddDays(-1);
Response.Redirect("/MySite", true);
-- Srini Kella, August 21, 2003
 
Hi, it was a great tutorial untill i came to the "Securing Directories with Role Based Auth" part .

first of all it worked fine untill i created the subdirs.,.,

can somebody please explain me in detail if those directories need to be physical subdir's(which case we get compilation error as its not set up as a virtualdir)
if i set up a virtual directory and include the subdirectory as the directory path it still dont work. (it keeps redirecting me to the login page!!!") what am i missing???

oh yaa i tried using one web.config file and also individaul in saperate virtual sirtes notheng!!
please help.

do i have to create a new project and in that project config file do the changes or should i create a new physical folder by going thro Add->new ->folder in solution explorer.

etc.,
thanks
-- serpico ash, October 02, 2003
 
i have narrowed down the problem.,,
i can get the value of redirect sting correctly.,.,
but when i use redirection it is sending me back to the login page so my guess is the role is not being passed to the subdirectory web.config file.

any hints.,.,?? help pleeeeeeeeeeese!!
thanks
-- serpico ash, October 02, 2003
 
This is very good tutorial, who don't the basics of web designing.it describes the features of web designing and could have explained in more details because their is no explaination for the roll based too.
definately a 5 in high quality.........lavakumar.s.r
-- lava kumar, October 21, 2003
 
Excellent article, will be making good use of it, however, I can't seem to get the logged in role state to persist. is there any way to do this?

Thanks for your help.
-- Tim Thomas, April 21, 2004
 
Copyright © 2001 DevHood® All Rights Reserved