Understanding Authentication Logic

   

Now that your application knows that you are using forms-based security, you need to start configuring the application to handle authentication the way you want.

One thing I must mention now is that the techniques discussed in this chapter require that the client's machine be running a cookie-capable and -enabled browser. If this is in question, you need to fabricate an alternate method for authentication. Cookies are required for ASP.NET form-based authentication.

The next thing I want to address is the next tag in the Web.Config file. This is called the <forms> tag and is to be located inside the <authentication> tag. It has four attributes that I'll cover: name, loginURL, protection, and timeout.

Web.Config
<configuration>      <system.web>              <authentication mode="Forms">                  <forms name="NewRiderAuth"                  loginUrl="ch12/login.aspx"                  protection="All"                  timeout="15">              </forms>              </authentication>      </system.web>  </configuration> 

The name attribute specifies the name of the HTTPCookie used for authentication. By default this is .ASPXAUT. If you need to run multiple applications under a single domain, and need these applications to run with a unique cookie, this is where you would set it.

The loginURL attribute specifies the URL where individuals should be redirected if they haven't been authenticated yet. If none is specified, the default value is default.aspx.

The protection attribute has four different possible options: All, None, Encryption, and Validation. The authorization cookie can be encrypted and validated to protect against tampering. You can specify one or the other, both, or none with the different options.

The timeout attribute specifies the time in minutes when the authorization cookie expires, from the time of the last request received. The default is 30 minutes.

Now we have come to a crossroads with regard to what trusted source you are going to use to authenticate a user. In traditional ASP, a database was often used as the source. A user would enter a username and password into a form, and this would be compared against values in a database. The username was compared to a username column, and a password was compared to a password column. If there was a matching record in the database, the user was considered authenticated.

ASP.NET also provides another way to authenticate with form-based authentication, which is to compare a user's input against values in the Web.Config value. I'll explore this method first, then show you how to use a database as the trusted source.

Authenticating Against the Web.Config file.

As I mentioned before, the Web.Config file can hold user data that you can authenticate against, and this is a valid ASP.NET form of validation. The way users are stored is with the addition of two more tags that are placed inside the <forms> tag in the Web.Config file. These tags are the <credentials> tag, and nested within it are <user> tags, one for each user.

Web.Config
<configuration>      <system.web>              <authentication mode="Forms">                  <forms name="NewRiderAuth"                  loginUrl="ch12/login.aspx"                  protection="All"                  timeout="15">                  <credentials passwordFormat="Clear">                      <user name="Peter" password="NewRiders"/>                          <user name="Tom" password="qwerty"/>                  </credentials>              </forms>              </authentication>      </system.web>  </configuration> 

I will address the passwordFormat attribute of the <credentials> tag in a little bit. In the <user> tag, there are simply a name attribute and a password attribute that are used for the comparison for authentication.

Using the Authenticate() Method

Now on to the authentication. I'm not going to get too technical here, but I have to explain a few things. There is a class called FormsAuthentication that handles just about everything we're going to investigate here. Normally, to be able to access an object's methods, you've had to create an instance of that object. If the object was in a namespace that wasn't inherently available in the ASP.NET program, such as System.Web.UI, you've had to import that namespace to create and use that object. We are faced with an exception to this rule here. There are methods of objects that are available for use in ASP.NET pages without importing any namespace and even without creating an instance of an object. These are called shared/static methods in Visual Basic .NET and static methods in C#.

In a nutshell, all this to say that shared/static methods are always available for use; all you have to do is use a Class.Method() syntax. For the sake of brevity, from now on I will refer to these as "static" methods and drop the "shared" from my explanations. A lot of static methods of the FormsAuthentication object are used throughout this chapter.

In the following example, I am going to introduce you to the first static method, which is a member of the FormsAuthentication object. It is called Authenticate. It takes two parameter: name and password, which are compared against the name and password values in the <user> tag of the Web.Config file. Take a look.

Visual Basic .NET authen_simple_vb.aspx
<%@ page language="vb" runat="server"%>  <script runat=server>  Sub Authenticate(Sender as Object, e as EventArgs)      if FormsAuthentication.Authenticate (Username.Text, Password.Text) then          OurLabel.Text = "This is a valid login"      Else          OurLabel.Text = "This login doesn't exist"      End If  End Sub  </script>  <html>  <head>    <title>Login</title>  </head>  <body bgcolor="#FFFFFF" text="#000000">  <form runat="server">  <h3>Login</h3>  <asp:textbox  runat="server" /> Username<br>  <asp:textbox  runat="server" />  Password <br><br>  <asp:button text="Login" onClick="Authenticate" runat="server" /><br><br>  <asp:Label  EnableViewState="False" runat="server" />  </form>  </body>  </html> 
C# authen_simple_cs.aspx
<%@ page language="cs" runat="server"%>  <script runat=server>  void Authenticate(Object Sender, EventArgs e){     if (FormsAuthentication.Authenticate (Username.Text, Password.Text)){         OurLabel.Text = "This is a valid login";      }else{         OurLabel.Text = "This login doesn't exist";      }  }  </script>  <html>  <head>  <title>Login</title>  </head>  <body bgcolor="#FFFFFF" text="#000000">  <form runat="server">  <h3>Login</h3>  <asp:textbox  runat="server" /> Username<br>  <asp:textbox  runat="server" />  Password <br><br>  <asp:button text="Login" onClick="Authenticate" runat="server" /><br><br>  <asp:Label  EnableViewState="False" runat="server" />  </form>  </body>  </html> 

As you can see, this is very simple. Simply pass the text of the Username and Password text boxes, and the Authenticate static method of FormsAuthentication compares values against all the <user> tags in our Web.Config file. You can see the results in Figure 12.1.

Figure 12.1. The Forms Authentication.Authenticate static method allows you to easily compare usernames and passwords against <user> data stored in the Web.Config file.
graphics/12fig01.gif

Note

When you compare usernames and passwords against the Web.Config <user> entries using the Authenticate static method, be aware that it is case-sensitive, requiring that the usernames and passwords match precisely.


Using Hashed Passwords in Web.Config

This is a great method if only trusted people have access to your Web.Config file, because the passwords to your application are exposed. But this doesn't seem very secure if your Web.Config file is located where people other than fully trusted individuals can access it. This means you can't use your favorite password that has been etched on the inside of your forehead.

The .NET Framework has made accommodation for this. Remember, I said that I would touch on the <credentials> tag earlier. In that tag was an attribute called passwordFormat that was set to Clear. The passwordFormat attribute has three possible values: Clear, SHA1, and MD5. The Clear value simply ensures that the password values in the <user> tags are written in plain text.

The SHA1 and MD5 options are cryptographic options called one-way hashes. "What's a hash?" you ask. The long and short of it is that a hash is basically a cryptographic digital fingerprint of a string. A string is converted into a fixed length hash that is a representation of that string in hash form. These two technologies are considered "one-way" hashes, which means that it is "computationally infeasible" to invert and derive the initial string from the hash. In other words, it's simple to take a password and create a hash from it, but it is nearly impossible to take a hash and re-create the password.

These two options for passwordFormat allow you to enter hashed passwords, rather than plain text, in the Web.Config file. This way anyone looking at the Web.Config file will see a password that really doesn't make any sense to them.

Now you are faced with creating hashed versions of your passwords. Again, the .NET Framework makes a way. Believe it or not, there is a static method of the FormsAuthentication object called HashPasswordForStoringInConfigFile(). You simply pass in the password and the hashtype to this method and it returns the hash of that password. The following is an ASP.NET page that I made that uses this static method to hash passwords.

Visual Basic .NET password_createhash_vb.aspx
<%@ page language="vb" runat="server"%>  <script runat=server>  Sub CreateHash(sender As Object, e As EventArgs)      Dim vHash As String      Dim vHashType As String = HashType.SelectedItem.Text      Dim vPassword As String = Password.Text      vHash = FormsAuthentication.HashPasswordForStoringInConfigFile(vPassword,vHashType)      OurLabel.Text = "Your hashed password in " + vHashType + " format"      OurHash.Text = vHash  End Sub 'CreateHash  </script>  <html>  <head>  <title>Create Password Hash</title>  </head>  <body bgcolor="#FFFFFF" text="#000000">  <form runat="server">  <h3>Hash Password for Web.Config File</h3>  Insert Password <br>  <asp:textbox  runat="server"/><br>  <asp:RadioButtonList  runat="server">  <asp:ListItem text="SHA1"/>  <asp:ListItem text="MD5"/>  </asp:RadiobuttonList>  <asp:Button Text="Create Hash" onClick="CreateHash" runat="server" /><br><br>  <u><asp:Label  runat="server" /></u><br>  <asp:Label  runat="server" />  </form>  </body>  </html> 
C# password_createhash_cs.aspx
<%@ page language="c#" runat="server"%>  <script runat=server>  void CreateHash(object sender, EventArgs e){     string vHash;      string vHashType = HashType.SelectedItem.Text;      string vPassword = Password.Text;      vHash = FormsAuthentication.HashPasswordForStoringInConfigFile(vPassword,vHashType);      OurLabel.Text = "Your hashed password in " + vHashType + " format";      OurHash.Text = vHash;  }  </script>  <html>  <head>  <title>Create Password Hash</title>  </head>  <body bgcolor="#FFFFFF" text="#000000">  <form runat="server">  <h3>Hash Password for Web.Config File</h3>  Insert Password <br>  <asp:textbox  runat="server"/><br>  <asp:RadioButtonList  runat="server">  <asp:ListItem text="SHA1"/>  <asp:ListItem text="MD5"/>  </asp:RadiobuttonList>  <asp:Button Text="Create Hash" onClick="CreateHash" runat="server" /><br><br>  <u><asp:Label  runat="server" /></u><br>  <asp:Label  runat="server" />  </form>  </body>  </html> 

Creating a hashed password in ASP.NET is simple . If you look at Figure 12.2 you can see I simply entered my desired password into the text box and the static method called HashPasswordForStoringInConfigFile() generates the appropriate hash for that password. I enter NewRiders into the text box and select MD5 as the type and it generates my password hash.

Figure 12.2. Making hashes of passwords is simple with the static method called HashPasswordForStoringInConfigFile().
graphics/12fig02.gif

I copy that password hash from the page and paste it into the password attribute of my user tag and set the passwordFormat to MD5.

Web.Config
<credentials passwordFormat="MD5">      <user name="Peter" password="F3018F9C7507F4A34DC8A1BD1A7DE9D3"/>  </credentials> 

No changes need to be made to my ASP.NET authentication page. All the changes are made in the Web.Config file. In Figure 12.3, you can see the expected results, when using the MD5 method. Using the SHA1 method is no different, except that you need to make SHA1 versions of hashed passwords and set the passwordFormat attribute of the <credentials> tag to SHA1.

Figure 12.3. Using a hash system of passwordFormat in the <credentials> tag in the Web.Config doesn't alter the way people log in to your site.
graphics/12fig03.gif

Basically what the .NET Framework does is hash the inserted password and compare that to the password entered in the <user> tag.

This offers another layer of protection with regard to security, but does leave some leeway for people that absolutely want to penetrate the system. This isn't really the best method if you have more than a handful of users, because it can become a bit of a cumbersome way to manage large numbers of users. If you need to manage many user accounts, I recommend using a database. If you need something that is a bit more secure, I recommend managing your users in a secure database environment such as Microsoft's SQL Server. I don't classify Microsoft Access as a secure database environment because there are many readily available utilities that can compromise its less-than-strong security features.

Authenticating Against a Database

As I mentioned, the Web.Config method of managing users is a fine method if security isn't paramount and you aren't dealing with a boatload of users. If either or both of these is the case, though, I recommend a database as your trusted source. I cover using both Microsoft Access and stored procedures in Microsoft SQL Server for authentication in this section. As with the previous section, I will be doing just the "comparison" portion of authentication here. What to do after the comparison process is done is covered in the next section.

For these demonstrations I've added a table called Logins to both the Microsoft Access and Microsoft SQL Server versions of the Northwind database. In Figure 12.4 you can see a screenshot of the database table in Access. It is exactly the same in the SQL Server version.

Figure 12.4. The database Logins table.
graphics/12fig04.gif
Using Microsoft Access as a Trusted Source

To authenticate against a database demands a slightly different approach than authenticating against the Web.Config file. What I generally do is create another function that calls to the database to compare the username and password. The following is a code example of this.

Visual Basic .NET authen_access_vb.aspx
<%@ page language="vb" runat="server"%>  <%@ Import Namespace="System.Data"%>  <%@ Import Namespace="System.Data.OleDb"%>  <script runat=server>  Sub Authenticate(Sender As [Object], e As EventArgs)      Dim Login_ID As Integer = DBAuthenticate(Username.Text, Password.Text)      If Login_ID > 0 Then          OurLabel.Text = "This is a valid login"      Else          OurLabel.Text = "This login doesn't exist"      End If  End Sub  Function DBAuthenticate(username As String, password As String) As Integer      Dim OurConnection As OleDbConnection      Dim OurCommand As OleDbCommand      Dim OurDataReader As OleDbDataReader      Dim SQLString As String      Dim Login_ID As Integer      SQLString = "Select Login_ID,Password from Logins where Username = '" + username + "'  graphics/ccc.gifand Password = '" + password + "'"      OurConnection = New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +  graphics/ccc.gifServer.MapPath("../_database/northwind.mdb"))      OurConnection.Open()      OurCommand = New OleDbCommand(SQLString, OurConnection)      OurDataReader = OurCommand.ExecuteReader()      If OurDataReader.Read() Then          If OurDataReader("Password").ToString() = password Then              Login_ID = Int32.Parse(OurDataReader("Login_ID").ToString())          Else              Login_ID = 0          End If      Else          Login_ID = 0      End If      OurDataReader.Close()      OurConnection.Close()      Return Login_ID  End Function  </script>  <html>  <head>  <title>Login</title>  </head>  <body bgcolor="#FFFFFF" text="#000000">  <form runat="server">  <h3>Login</h3>  <asp:textbox  runat="server" /> Username<br>  <asp:textbox  runat="server" />  Password <br><br>  <asp:button text="Login" onClick="Authenticate" runat="server" /><br><br>  <asp:Label  EnableViewState="False" runat="server" /><br>  </form>  </body>  </html> 
C# authen_access_cs.aspx
<%@ page language="cs" runat="server"%>  <%@ Import Namespace="System.Data"%>  <%@ Import Namespace="System.Data.OleDb"%>  <script runat=server>  void Authenticate(Object Sender, EventArgs e){     int Login_ID = DBAuthenticate(Username.Text, Password.Text);      if (Login_ID > 0){         OurLabel.Text = "This is a valid login";      }else{         OurLabel.Text = "This login doesn't exist";      }  }   int DBAuthenticate(string username,string password){      OleDbConnection OurConnection;      OleDbCommand OurCommand;      OleDbDataReader OurDataReader;      string SQLString;      int Login_ID;      SQLString = "Select Login_ID,Password from Logins where Username = '" + username + "'  graphics/ccc.gifand Password = '" + password + "'";      OurConnection = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +  graphics/ccc.gifServer.MapPath("../_database/northwind.mdb"));      OurConnection.Open();      OurCommand = new OleDbCommand(SQLString ,OurConnection);      OurDataReader = OurCommand.ExecuteReader();      if (OurDataReader.Read()){          if (OurDataReader["Password"].ToString() == password){                  Login_ID = Int32.Parse(OurDataReader["Login_ID"].ToString());          }else{                  Login_ID = 0;          }      }else{          Login_ID = 0;      }      OurDataReader.Close();      OurConnection.Close();      return Login_ID;  }  </script>  <html>  <head>  <title>Login</title>  </head>  <body bgcolor="#FFFFFF" text="#000000">  <form runat="server">  <h3>Login</h3>  <asp:textbox  runat="server" /> Username<br>  <asp:textbox  runat="server" />  Password <br><br>  <asp:button text="Login" onClick="Authenticate" runat="server" /><br><br>  <asp:Label  EnableViewState="False" runat="server" /><br>  </form>  </body>  </html> 

Although this may look complicated, it really isn't. The OnClick event of the button calls the Authenticate() function, which immediately sets an integer variable called Login_ID to the returned value of the DBAuthenticate() function.

In DBAuthenticate, the database is being queried for a record in the Logins table that matches the username and password that is entered in the text boxes. In the DBAuthenticate function, if there is a match, the line that reads if OurDataReader.Read() will return true. Then you know that you have a match that is not case-sensitive because T-SQL queries like this are not case-sensitive. If this returns false, set Login_ID in the DBAuthenticate to 0.

To make it a case-sensitive match, do a direct comparison with the line that reads If OurDataReader("Password").ToString() = password in Visual Basic .NET and if (OurDataReader["Password"].ToString() == password) in C#. This is a case-sensitive check. If the value returned from the database matches this check, then you have a total match and set the Login_ID in DBAuthenticate to the Login_ID from the database table; that is what gets returned to the Authenticate() function.

After you're back at the Authenticate function, evaluate the Login_ID. If it's 0, you know that the username and password didn't match; otherwise you'll see a value greater than 0 because the Login_IDs start at 1. Later I'll show you how to use this value at the Authentication cookie, which will make it a snap to pull user-specific data out of the database.

Using Microsoft SQL Server as a Trusted Source

Microsoft SQL Server provides a much more secure environment with which to store your login and password information. It can be protected from unauthorized users, the information is never exposed, and stored procedures allow you to query the database without ever having the username and password information leave the SQL Server database.

The authentication function you are going to explore is very similar to the Access version, except the username and password are sent to a stored procedure. You check for their existence and then return an output parameter called @Login_ID with the Login_ID from the table if they do exist and return @Login_ID with a value of 0 if they don't. Here is the stored procedure.

spLogin
CREATE PROCEDURE [spLogin]      @Username varchar(50),      @Password varchar(50),      @Login_ID int output  AS      if exists (Select * from Logins where username = @username and password = @Password)          Set  @Login_ID = (Select Login_ID from Logins where username = @username and  graphics/ccc.gifpassword = @Password)      else          Set @Login_ID = 0  GO 

You use the same exact functions as with the Access database, except you run the stored procedure and return the value of @Login_ID to the Authenticate function.

Visual Basic .NET authen_sql_vb.aspx
<%@ page language="vb" runat="server"%>  <%@ Import Namespace="System.Data"%>  <%@ Import Namespace="System.Data.SqlClient" %>  <script runat=server>  Sub Authenticate(Sender As [Object], e As EventArgs)      Dim Login_ID As Integer = DBAuthenticate(Username.Text, Password.Text)      If Login_ID > 0 Then          OurLabel.Text = "This is a valid login"      Else          OurLabel.Text = "This login doesn't exist"      End If  End Sub  Function DBAuthenticate(username As String, password As String) As Integer      Dim OurConnection As SqlConnection      Dim OurCommand As SqlCommand      Dim OurDataReader As SqlDataReader      OurConnection = New SqlConnection("Server=server;uid=newriders;pwd=password; graphics/ccc.gifdatabase=Northwind")      OurCommand = New SqlCommand("spLogin", OurConnection)      OurCommand.CommandType = CommandType.StoredProcedure      OurCommand.Parameters.Add("@Username", SqlDbType.VarChar, 50).Value = username      OurCommand.Parameters.Add("@Password", SqlDbType.VarChar, 50).Value = password      OurCommand.Parameters.Add("@Login_ID", SqlDbType.Int).Direction =ParameterDirection. graphics/ccc.gifOutput      OurConnection.Open()      OurCommand.ExecuteNonQuery()      Dim Login_ID As Integer = CInt(OurCommand.Parameters("@Login_ID").Value)      OurConnection.Close()      Return Login_ID  End Function  </script>  <html>  <head>  <title>Login</title>  </head>  <body bgcolor="#FFFFFF" text="#000000">  <form runat="server">  <h3>Login</h3>  <asp:textbox  runat="server" /> Username<br>  <asp:textbox  runat="server" />  Password <br><br>  <asp:button text="Login" onClick="Authenticate" runat="server" /><br><br>  <asp:Label  EnableViewState="False" runat="server" /><br>  </form>  </body>  </html> 
C# authen_sql_cs.aspx
<%@ page language="cs" runat="server"%>  <%@ Import Namespace="System.Data"%>  <%@ Import Namespace="System.Data.SqlClient" %>  <script runat=server>  void Authenticate(Object Sender, EventArgs e){     int Login_ID = DBAuthenticate(Username.Text, Password.Text);      if (Login_ID > 0){         OurLabel.Text = "This is a valid login";      }else{         OurLabel.Text = "This login doesn't exist";      }  }   int DBAuthenticate(string username,string password){     SqlConnection OurConnection;      SqlCommand OurCommand;      SqlDataReader OurDataReader;      OurConnection = new SqlConnection("Server=server;uid=newriders;pwd=password; graphics/ccc.gifdatabase=Northwind");      OurCommand = new SqlCommand("spLogin" ,OurConnection);      OurCommand.CommandType = CommandType.StoredProcedure;      OurCommand.Parameters.Add("@Username", SqlDbType.VarChar, 50).Value = username;      OurCommand.Parameters.Add("@Password", SqlDbType.VarChar, 50).Value = password;      OurCommand.Parameters.Add("@Login_ID", SqlDbType.Int).Direction = ParameterDirection. graphics/ccc.gifOutput;      OurConnection.Open();      OurCommand.ExecuteNonQuery();      int Login_ID =  (int)OurCommand.Parameters["@Login_ID"].Value;      OurConnection.Close();      return Login_ID;  }  </script>  <html>  <head>  <title>Login</title>  </head>  <body bgcolor="#FFFFFF" text="#000000">  <form runat="server">  <h3>Login</h3>  <asp:textbox  runat="server" /> Username<br>  <asp:textbox  runat="server" />  Password <br><br>  <asp:button text="Login" onClick="Authenticate" runat="server" /><br><br>  <asp:Label  EnableViewState="False" runat="server" /><br>  </form>  </body>  </html> 

Although this happens in a different way when you use the stored procedure, the logic isn't very different from the Access version. One thing to note is that although this is a more secure means of storing your sensitive username and password information, you may lose the capacity for case-sensitive authentication, depending on how the database is set up. I have never really found issue with this, but it is something to consider when designing your authentication process.

Completing the Authentication Process

When a user logs in, I highly doubt that all you want to do is set the Text value of a Label as I've done in the past examples. And actually this is only part of the process for ASP.NET to recognize that a user is considered authenticated.

This brings us back to some of the attributes of the <form> tag in the Web.Config file. Let's investigate the name and loginUrl attributes a bit.

 <authentication mode="Forms">      <forms name="NewRiderAuth"          loginUrl="ch12/authen_redirect_vb.aspx"          protection="All"          timeout="15">          <credentials passwordFormat="Clear">              <user name="Peter" password="NewRiders"/>                  <user name="Tom" password="qwerty"/>          </credentials>      </forms>  </authentication> 

For ASP.NET to consider a user authenticated, that user must have a cookie that matches the name attribute in the <form> tag. Remember that this is the name you are specifying that ASP.NET is to use for the authentication cookie. Also remember that this cookie will be encrypted and validated by ASP.NET when it's created and retrieved, because the protection attribute has been set to All.

The loginUrl attribute tells ASP.NET what page is used to perform the authentication process, or in other words, your login page. The login page basically handles when a user who is not authenticated goes to a page that requires authentication to view. Those users are automatically sent to the URL set in loginUrl if you are using forms security function.

To demonstrate what happens, I have touched on authorization a bit. I have created a directory called "protected" inside my ch12 directory and placed a Web.Config file inside that directory that prevents unauthenticated users from viewing any pages in that directory. The following is the Web.Config file that is in that "protected" directory.

ch12/protected/Web.Config
<configuration>      <system.web>          <authorization>              <deny users="?" />          </authorization>      </system.web>  </configuration> 

The question mark (?) in the <deny users="?" /> tag represents anonymous users. I will explain all of this in detail later in this chapter, but basically this authorization says to deny all anonymous users access to this directory and everything contained inside it, including subdirectories that don't have a Web.Config file with explicit authorization settings of its own.

Tip

Every directory within your application can contain its own Web.Config file to configure the directory that contains it. I recommend that you learn more about configuring your ASP.NET applications by investigating the following URL for the ASP.NET Configuration section in the .NET Framework SDK.

ms-help://MS.NETFrameworkSDK/cpguidenf/html/cpconaspnetconfiguration.htm


I've create a page inside the protected directory called test_redirect.aspx, which for the purpose of this example is only in Visual Basic .NET.

Test_redirect.aspx
<%@ page language="vb" runat="server"%>  <script runat=server>  Sub Page_Load()      OurLabel.Text = User.Identity.Name  End Sub  </script>  <html>  <head>  <title>Protected Page</title>  </head>  <body bgcolor="#FFFFFF" text="#000000">  Your user cookie reads: <asp:label  runat="server" />  </body>  </html> 

Notice the highlighted text that reads User.Identity.Name. To put it simply, it is a shortcut to the Authentication cookie in the name attribute of the <forms> tag in the root Web.Config file. Basically, what this page does is set a Label's text property to the value of that cookie.

You can see what happens if I attempt to browse to this file directly in Figure 12.5. Because it resides in a directory that doesn't allow anonymous users (remember the question mark in the <deny> tag) I get rejected and redirected to the URL set in the loginUrl attribute of the <forms> tag in the root Web.Config. In essence, any page that I protect with the ASP.NET authentication function will force me to the page at loginUrl if I'm not authenticated.

Figure 12.5. Going to a page that is protected against unauthenticated users redirects you to the page located in the loginUrl attribute of the <forms> tag in the application's root Web.Config file.
graphics/12fig05.gif

If you look at the address bar in Figure 12.4, you can also see that there is a querystring value called ReturnUrl with a value of %2fch12%2fprotected%2ftest_redirect.aspx. This is a URLEncoded version of the path back to the page I was rejected from, which is the test_ redirect.aspx. If you carefully look at the gobbledygook value of ReturnUrl, you can see that the page's title is there. The %2f symbols that litter the value are actually the URLEncoded value for a forward slash (/). If you replace the %2f symbols in ReturnUrl with forward slashes (/) the result is /ch12/protected/ test_redirect.aspx. So ASP.NET maintains where you came from when you were rejected. If you are successfully authenticated, ASP.NET has the capability to automatically return you to the URL from which you were originally rejected. Very nice feature.

Using the RedirectFromLoginPage() Method

This leads us to the changes in the authentication process. The following is a modified login page that finishes the authentication process according to ASP.NET.

Visual Basic .NET authen_redirect_vb.aspx
<%@ page language="vb" runat="server"%>  <script runat=server>  Sub Authorize(Sender as Object, e as EventArgs)      if FormsAuthentication.Authenticate (Username.Text, Password.Text) then          FormsAuthentication.RedirectFromLoginPage (Username.Text, False)      Else          OurLabel.Text = "This login doesn't exist"      End If  End Sub  </script>  <html>  <head>  <title>Login</title>  </head>  <body bgcolor="#FFFFFF" text="#000000">  <form runat="server">  <h3>Login</h3>  <asp:textbox  runat="server" /> Username<br>  <asp:textbox  runat="server" />  Password <br><br>  <asp:button text="Login" onClick="Authorize" runat="server" /><br><br>  <asp:Label  EnableViewState="False" runat="server" />  </form>  </body>  </html> 
C# authen_redirect_cs.aspx
<%@ page language="cs" runat="server"%>  <script runat=server>  void Authorize(Object Sender, EventArgs e){     if (FormsAuthentication.Authenticate (Username.Text, Password.Text)){         FormsAuthentication.RedirectFromLoginPage (Username.Text, false);      }else{         OurLabel.Text = "This login doesn't exist";      }  }  </script>  <html>  <head>  <title>Login</title>  </head>  <body bgcolor="#FFFFFF" text="#000000">  <form runat="server">  <h3>Login</h3>  <asp:textbox  runat="server" /> Username<br>  <asp:textbox  runat="server" />  Password <br><br>  <asp:button text="Login" onClick="Authorize" runat="server" /><br><br>  <asp:Label  EnableViewState="False" runat="server" />  </form>  </body>  </html> 

Now here's where the magic happens. Notice another static method of FormsAuthentication called RedirectFromLoginPage(). This method does two things when it's called. First, it sets the authentication cookie with the name assigned in the name attribute in the <form> tag of the Web.Config file. You send two parameters with this method to accomplish this. The first one is the value that the authentication cookie should be set to. In the case of the preceding example, this is set to the username that is retrieved from the Username text box's Text property. The second parameter you send is Boolean, true or false, to indicate whether the authentication cookie should persist across browser sessions.

When this parameter is set to true, the cookie's expiration is set to DateTime.Now plus 50 years. No matter how good computers are, I can assure you that your computer and mine will expire long before this cookie does. If you set this parameter to false, the authentication doesn't persist across sessions and is set in memory only.

After the RedirectFromLoginPage() method sets the cookies, it redirects you back to the value of ReturnUrl in the QueryString.

In Figure 12.6 you can see that after I was successfully authenticated I was sent to the page from which I was originally rejected.

Figure 12.6. After you are properly authenticated, the RedirectFromLoginPage() method automatically returns you to the page from which you were originally rejected.
graphics/12fig06.gif
Building Your Own Redirect

Fifty years is a long time for you to wait for a cookie to expire before you force someone to re-authenticate to gain access to restricted areas of your site. But the RedirectFromLoginPage() offers only one way to persist the authentication cookie, and I think we all agree that it may be a bit too long for some people.

Thankfully, the .NET Framework provides everything we need. Instead of just calling the RedirectFromLoginPage() method with the Username and persisting information, you have to use two different methods to manipulate the cookie and redirect properly.

It is important for you to understand that the Authentication cookie actually has two different expirations dates. One is the date to which the Persist attribute is tied. This expiration is inside the cookie in an encrypted form. This value can possibly contain two different things. If the Authentication cookie is set to persist, this expiration is set to 50 years from the time the cookie was created. If the cookie isn't set to persist, this is set ahead of the time it was created by the number of minutes in the timeout attribute in the <form> tag in the root Web.Config file. The other expiration is the one that the client's browser normally deals with in controlling cookie expiration. The .NET Framework sets this expiration to 1/1/0001 12:00 A.M by default. Apparently around 8000 years from now.

What I'm about to explain is "cheating" by overriding the encrypted expiration of the authentication cookie with the browser's expiration, basically making the cookie inaccessible to the .NET framework because the browser sees it as expired. The encrypted expiration is still set to +50 years from creation. Later you will see how to manipulate the encrypted data in the cookie, but for now we'll cheat.

In this example I'm also going to give users the choice to have their cookies persist or not by providing a check box in which they can indicate their preference.

Visual Basic .NET authen_cookie_cs.aspx
<%@ page language="vb" runat="server"%>  <script runat=server>  Sub Authorize(Sender As [Object], e As EventArgs)      If FormsAuthentication.Authenticate(Username.Text, Password.Text) Then          Dim cookie As HttpCookie = FormsAuthentication.GetAuthCookie(Username.Text, graphics/ccc.gifPersistCookie.Checked)          cookie.Expires = DateTime.Now.AddDays(14)          Response.Cookies.Add(cookie)          Response.Redirect(FormsAuthentication.GetRedirectUrl(Username.Text,PersistCookie. graphics/ccc.gifChecked))      Else          OurLabel.Text = "This login doesn't exist"      End If  End Sub 'Authorize  </script>  <html>  <head>  <title>Login</title>  </head>  <body bgcolor="#FFFFFF" text="#000000">  <form runat="server">  <h3>Login Page</h3>  <asp:textbox  runat="server" /> Username<br>  <asp:textbox  runat="server" />  Password <br>  <asp:checkbox  runat="server" />  Save your login status<br><br>  <asp:button text="Login" onClick="Authorize" runat="server" /><br><br>  <asp:Label  EnableViewState="False" runat="server" />  </form>  </body>  </html> 
C# authen_cookie_cs.aspx
<%@ page language="cs" runat="server"%>  <%@ Import Namespace="System.Threading"%>  <%@ Import Namespace="System.Security.Principal"%>  <script runat=server>  void Authorize(Object Sender, EventArgs e){     if (FormsAuthentication.Authenticate (Username.Text, Password.Text)){         HttpCookie cookie =FormsAuthentication.GetAuthCookie(Username.Text,PersistCookie. graphics/ccc.gifChecked);          cookie.Expires = DateTime.Now.AddDays(14);          Response.Cookies.Add (cookie);          Response.Redirect (FormsAuthentication.GetRedirectUrl (Username.Text, graphics/ccc.gifPersistCookie.Checked));      }else{         OurLabel.Text = "This login doesn't exist";      }  }  </script>  <html>  <head>  <title>Login</title>  </head>  <body bgcolor="#FFFFFF" text="#000000">  <form runat="server">  <h3>Login Page</h3>  <asp:textbox  runat="server" /> Username<br>  <asp:textbox  runat="server" />  Password <br>  <asp:checkbox  runat="server" />  Save your login status<br><br>  <asp:button text="Login" onClick="Authorize" runat="server" /><br><br>  <asp:Label  EnableViewState="False" runat="server" />  </form>  </body>  </html> 

To replace the RedirectFromLoginPage() method, I create a variable of the HttpCookie type. Then I use the GetAuthCookie() method, which creates the authentication cookie and sets the cookie variable to the Authentication cookie. Then I set the expiration to DateTime.Now plus 14 days and use the HttpCookie object's Add() method, which adds the cookie to the response back to the client. I then use another method called GetRedirectUrl, which retrieves the value of RedirectUrl from the QueryString and then redirects the user to that page. At that time the cookie is written to the client's machine.

In Figure 12.7 you can see the form with the Checkbox server control that lets the user decide whether the cookie should persist or not. In Figure 12.8 you can see the results of this page when authentication takes place with the check box checked. Look at the two expiration dates. The encrypted expiration is set to 50 years from creation, but it is overridden with the browser's expiration.

Figure 12.7. Form authentication gives you a little play to set cookie expiration for the Authentication cookie.
graphics/12fig07.gif
Figure 12.8. Notice that the Authentication cookie has two different expiration dates: one that's contained in the encrypted cookie and one in the browser's normal expiration.
graphics/12fig08.gif

As I mentioned, you can manipulate the contents of an encrypted cookie, including the expiration. I will explain that later in this chapter. If you prefer to manipulate only the expiration that the .NET Framework deals with, you can use the methods I will explain later to do this.


   
Top


ASP. NET for Web Designers
ASP.NET for Web Designers
ISBN: 073571262X
EAN: 2147483647
Year: 2005
Pages: 94
Authors: Peter Ladka

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net