BASICS: Forms Authentication in ASP.NET 2.0

Covers the basics of Forms Authentication whether used with the Membership and Roles Providers, or as a standalone authentication / authorization mechanism. This is the first of a series of "BASICS" articles. If you have a suggestion, please feel free to post about it.

ASP.NET offers an infrastructure for authentication and authorization that is designed to meet most of the needs of developers in securing an application. You have the choice of Forms, Windows, and Passport authentication. Of the three, Forms Authentication is the most widely used and provides an extensible means to hook in to the Membership and Roles providers or not, as the needs of the application may dictate.

If none of the built-in authentication schemes provided by ASP.NET meet your needs, the ASP.NET 2.0 framework provides the ability to create your own authentication scheme by writing a custom class that implements the IAuthenticationProvider interface, and registering it to take over from the built-in .NET authentication schemes.

Forms authentication can be used against a database without the need for registering either or both of the Membership or Roles providers, and in many cases this may be the preferred route. One example is where you may be using a Forums or messageboard application in your web site which has its own Users database table for Forms Authentication, and, in order to avoid duplication across database tables, you wish to provide a main site login facility that also uses the same table. This can be done easily with Forms Authentication, and it can even be done across different ASP.NET applications via the enableCrossAppRedirects property.

Forms Authentication in ASP.NET 2.0 is performed via the FormsAuthenticationModule class, which constructs a GenericPrincipal object and stores it in the HTTP context. The GenericPrincipal object holds a reference to a FormsIdentity instance that represents the currently authenticated user. Normally, you should allow forms authentication to manage these tasks for you.

In this code, you would construct a custom IPrincipal object that wraps the FormsIdentity object, and then store it in the HttpContext.User property. If you do this, you will also need to set the IPrincipal reference on the Thread.CurrentPrincipal property. This ensures that both the HttpContext object and the thread point to the same authentication information.

I have seen many times where developers implement their own database lookup for authentication and authorization, storing various "logged in" properties in Session State. This is both redundant and unncessary if you simply take the time to learn how Forms Authentication works, and you can get a lot richer site experience by using the built-in IPrincipal object with Forms Authentication as described above.

Basic Forms Authentication

You can implement basic Forms Authentication in your application by simply setting this element as follows:

<authentication mode="Forms" />

This would implement Forms Authentication using the default properties specified in the Master web.config file:

<system.web>
<authentication mode="Forms">
<forms loginUrl="Login.aspx"
protection="All"
timeout="30"
name=".ASPXAUTH"
path="/"
requireSSL="false"
slidingExpiration="true"
defaultUrl="default.aspx"
cookieless="UseDeviceProfile"
enableCrossAppRedirects="false" />
</authentication>
</system.web>

  • loginUrl points to your application's custom logon page.
  • protection is set to All to specify privacy and integrity for the forms authentication ticket. This causes the authentication ticket to be encrypted using the algorithm specified on the machineKey element, and to be signed using the hashing algorithm that is also specified on the machineKey element. If you use multiple Applications with enableCrossAppRedirects, the machineKey element must be identical in each application. You cannot use "autogenerate".  In addition, the cookieName and path must be the same in each application.
  • timeout is used to specify a limited lifetime for the forms authentication session. The default value is 30 minutes. If a persistent forms authentication cookie is issued, the timeout attribute is also used to set the lifetime of the persistent cookie. This is NOT the same as the Session cookie- they are independent and different.
  • name and path are set to the values defined in the application's configuration file.
  • requireSSL is set to false. This configuration means that authentication cookies can be transmitted over channels that are not SSL-encrypted. If you are concerned about session hijacking, you should consider setting requireSSL to true.
  • slidingExpiration is set to true to enforce a sliding session lifetime. This means that the session timeout is periodically reset as long as a user stays active on the site.
  • defaultUrl is set to the Default.aspx page for the application.
  • cookieless is set to UseDeviceProfile to specify that the application use cookies for all browsers that support cookies. If a browser that does not support cookies accesses the site, then forms authentication packages the authentication ticket on the URL.
  • enableCrossAppRedirects is set to false to indicate that forms authentication does not support automatic processing of tickets that are passed between applications on the query string or as part of a form POST.

Most examples of Forms Authentication assume that you want to restrict acess to only authenticated users. However, it is often the case that we only want to provide a means to authenticate so that the User Identity that is obtained can be used in forum applications or other areas of the site where a "membership" identity is needed. In this scenario unauthenticated users are given run of the site and only required to login in order to be able to make messageboard posts, for example. 

In a highly simplified scenario, you could actually put a set of credentialis right into the Forms element as follows, and not even use a database or other store for authentication:

 <credentials passwordFormat = "SHA1"
               <user name="UserName1" password="SHA1EncryptedPassword1"/>
               <user name="UserName2" password="SHA1EncryptedPassword2"/>
               <user name="UserName3" password="SHA1EncryptedPassword3"/>
 </credentials>

Authorization Configuration

In IIS, anonymous access is enabled for all applications that use forms authentication. The UrlAuthorizationModule class is used to help ensure that only authenticated users can access a page. You could configure UrlAuthorizationModule by using the authorization element as shown in the following example.

<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>

With this setting, all unauthenticated users are denied access to any page in your application. If an unauthenticated user tries to access a page, the forms authentication module redirects the user to the login page specified by the loginUrl attribute of the forms element in your web.config file. In the scenario where we only want to require authentication and authorization for certain portions of our application, we can use the location path element:

<location path="Forums">
    <system.web>
      <authorization> 
        <deny users="?"/>
      </authorization>       
    </system.web>
     </location>
 

Note
that the above contains its own unique "system.web" node, in addition to the main one that is already in your web.config file. This means that anyone wanting to visit anything in the "Forums" subfolder would need to login, but they would still have unauthenticated free run of the site for all other areas.

Sample Forms authentication control flow

1. The user requests the Default.aspx file from your application's virtual directory. IIS allows the request because anonymous access is enabled in the IIS metabase. ASP.NET confirms that the authorization element includes a <deny users="?" /> tag.

2. The server looks for an authentication cookie. If it fails to find the authentication cookie, the user is redirected to the configured logon page (Login.aspx), as specified by the LoginUrl attribute of the forms element. The user supplies and submits credentials through this form. Information about the originating page is placed in the query string using RETURNURL as the key. The server HTTP reply is as follows:

3. 302 Found Location: http://yoursite/yourapp/login.aspx?RETURNURL=%2fyourapp%2fDefault.aspx

4. The browser requests the Login.aspx page and includes the RETURNURL parameter in the query string.

5. The server returns the logon page and the 200 OK HTTP status code.

6. The user enters credentials on the logon page and posts the page, including the RETURNURL parameter from the query string, back to the server.

7. The server validates user credentials against a store, such as a SQL Server database or an Active Directory user store. Code in the logon page creates a cookie that contains a forms authentication ticket that is set for the session.

In ASP.NET 2.0, the validation of user credentials can be performed by the membership system. The Membership class provides the ValidateUser method for this purpose as shown here:

if (Membership.ValidateUser(userName.Text, password.Text))
{
if (Request.QueryString["ReturnUrl"] != null)
{
FormsAuthentication.RedirectFromLoginPage(userName.Text, false);
}
else
{
FormsAuthentication.SetAuthCookie(userName.Text, false);
}
}
else
{
Response.Write("Invalid UserID and Password");
}

But, if you are not using the Membership provider, one way to perform custom database authentication is to implement the code in Global.asax, in Application_AuthenticateReqeuest, which fires on every page requested. This is what determines authentication when a page is requested:

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)
{
// Get Forms Identity From Current User
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
// Get Forms Ticket From Identity object
FormsAuthenticationTicket ticket = id.Ticket;
// userdata string was retrieved from stored user-data (a roles string from db "Users" table, e.g. "Admin;Manager;User")
string userData = ticket.UserData;
string[] roles = userData.Split(',');
// Create a new Generic Principal Instance and assign to Current User
HttpContext.Current.User = new GenericPrincipal(id, roles); // (could also be a custom principal object of your design)
}
}
}
}

This is complemented by our Login method that is run when a user wants to log in and supplies a valid UserName and password:

private void btnLogin_Click(object sender, System.EventArgs e)
{
// Initialize FormsAuthentication (reads the configuration and gets
// the cookie values and encryption keys for the given application)
FormsAuthentication.Initialize();

// Create connection and command objects
SqlConnection conn =
new SqlConnection("Data Source=(local);Database=YourDb;User ID=user;password=pass;");
conn.Open();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "SELECT roles FROM Users WHERE username=@username " +
"AND password=@password"; // (this should really be a stored procedure, shown here for simplicity)

// Fill our parameters
cmd.Parameters.Add("@username", SqlDbType.NVarChar, 64).Value = txtUserName.Text;
cmd.Parameters.Add("@password", SqlDbType.NVarChar, 64).Value = txtPassword.Text;
FormsAuthentication.HashPasswordForStoringInConfigFile(txtPassword.Text,"sha1");
// you can use the above method for encrypting passwords to be stored in the database
// Execute the command
SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
// Create a new ticket used for authentication
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // Ticket version
TextBox1.Text, // Username to be associated with this ticket
DateTime.Now, // Date/time issued
DateTime.Now.AddMinutes(30), // Date/time to expire
true, // "true" for a persistent user cookie (could be a checkbox on form)
reader.GetString(0), // User-data (the roles from this user record in our database)
FormsAuthentication.FormsCookiePath); // Path cookie is valid for

// Hash the cookie for transport over the wire
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(
FormsAuthentication.FormsCookieName, // Name of auth cookie (it's the name specified in web.config)
hash); // Hashed ticket

// Add the cookie to the list for outbound 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 = "LoggedIn.aspx";

// Don't call the FormsAuthentication.RedirectFromLoginPage here, since it could
// replace the custom authentication ticket we just added...
Response.Redirect(returnUrl);
}
else
{
// Username and or password not found in our database...
ErrorLabel.Text = "Username / password incorrect. Please login again.";
ErrorLabel.Visible = true;
}
// (normally you'd put all this db stuff in a try / catch / finally block)
reader.Close();
conn.Close();
cmd.Dispose();
}

You can use the above code with a Login control, or you can make your own little "login" form as above.

8. Following the redirection, the browser requests the Default.aspx page again. This request includes the forms authentication cookie.

9. The FormsAuthenticationModule class detects the forms authentication cookie and authenticates the user. After successful authentication, the FormsAuthenticationModule class populates the current User property, which is exposed by the HttpContext object, with information about the authenticated user. In the above example, this is done in the Application_AuthenticateRequest method.

10. Since the server has verified the authentication cookie, it grants access and returns the Default.aspx page.

In this previous article, I show how it is possible to construct a custom IPrincipal object for the logged in User to be used with Forms Authentication.


Summary


Forms Authentication is highly configurable and can be used "Standalone" or in combination with the Membership and Role providers. You can also "roll your own" custom IPrincipal object to work with Forms Authentication and hold custom properties as a surrogate for what the Membership provider does.

Related Articles:

Roles and Membership

Table and Stored Procedure Profile Providers

SQLite 3 Membership and Roles Providers

By Peter Bromberg   Popularity  (25041 Views)