Chapter 20

Security

WHAT’S IN THIS CHAPTER?

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

Please note that all the code examples in this chapter are available as a part of this chapter’s code download on the book’s website at www.wrox.com on the Download Code tab.

Not every page that you build with ASP.NET is meant to be open and accessible to everyone on the Internet. Sometimes, you want to build pages or sections of an application that are accessible to only a select group of your choosing. For this reason, you need the security measures explained in this chapter. They can help protect the data behind your applications and the applications themselves from fraudulent use.

Security is a very wide-reaching term. During every step of the application-building process, you must, without a doubt, be aware of how mischievous end users might attempt to bypass your lockout measures. You must take steps to ensure that no one can take over the application or gain access to its resources. Whether it involves working with basic server controls or accessing databases, you should be thinking through the level of security you want to employ to protect yourself.

How security is applied to your applications is a measured process. For instance, a single ASP.NET page on the Internet, open to public access, has different security requirements than does an ASP.NET application that is available only to selected individuals because it deals with confidential information, such as credit card numbers or medical information.

The first step is to apply the appropriate level of security for the task at hand. Because you can take so many different actions to protect your applications and the resources, you have to decide for yourself which of these measures to employ. This chapter looks at some of the possibilities for protecting your applications.

Notice that security is discussed throughout this book. In addition, a couple of chapters focus on specific security frameworks provided by ASP.NET that are not discussed in this chapter. Chapters 18 and 19 discuss ASP.NET’s membership and role management frameworks, as well as the personalization features in this version. These topics are aspects of security that can make building safe applications even easier for you. Although these security frameworks are provided with this latest release of ASP.NET, you can still build your own measures as you did in the previous versions of ASP.NET. This chapter discusses how to do so.

An important aspect of security is how you handle the authentication and authorization for accessing resources in your applications. Before you begin working through some of the authentication/authorization possibilities in ASP.NET, which is what this chapter covers, you should know exactly what we mean by those two terms:

APPLYING AUTHENTICATION MEASURES

ASP.NET provides many different types of authentication measures to use within your applications, including basic authentication, digest authentication, forms authentication, and Integrated Windows authentication. You also can develop your own authentication methods. You should never authorize access to resources you mean to be secure if you have not applied an authentication process to the requests for the resources.

The different authentication modes are established through settings that can be applied to the application’s web.config file or in conjunction with the application server’s Internet Information Services (IIS) instance.

ASP.NET is configured through a series of .config files on the application server. These XML-based files enable you to easily change how ASP.NET behaves. Having these settings sit within an XML-based file is an ideal way to work with the configuration settings you require. ASP.NET configuration files are applied in a hierarchical manner. The .NET Framework provides a server-level configuration file called the machine.config file, which you can find at C:\Windows\Microsoft.NET\Framework\v4.0.xxxxx\CONFIG. The folder contains the machine.config file. This file provides ASP.NET application settings at a server level, meaning that the settings are applied to every ASP.NET application that resides on the particular server.

A web.config file is another XML-based configuration file that resides in the root directory of the web application. The settings applied in the web.config file override the same settings applied in the higher-level machine.config file.

You can even nest the web.config files so that the main application web.config file is located in the root directory of your application, but additional web.config files reside in some of the application’s subdirectories (see Figure 20-1). The web.config files contained in any of the subdirectories supersede the root directory’s web.config file. Therefore, any settings applied through a subdirectory’s web.config file change whatever was set in the application’s main web.config file.

FIGURE 20-1

image

In many of the examples in this chapter, you use the web.config file to apply the authentication and authorization mechanics you want in your applications. You also can work with IIS to apply settings directly to your applications.

IIS is the web server that handles all the incoming HTTP requests that come into the server. You must modify IIS to perform as you want. IIS hands a request to the ASP.NET engine only if the page has a specific file extension (for example, .aspx). In this chapter, you will work with IIS 7.x and 8, as well.

The <authentication> Node

You use the <authentication> node in the application’s web.config file to set the type of authentication your ASP.NET application requires:

<system.web>
   <authentication mode="Windows|Forms|Passport|None">
        
   </authentication>
</system.web>

The <authentication> node uses the mode attribute to set the form of authentication that is to be used. Options include Windows, Forms, Passport, and None. Each option is explained in Table 20-1.

TABLE 20-1

PROVIDER DESCRIPTION
Windows Windows authentication is used together with IIS authentication. Authentication is performed by IIS in the following ways: basic, digest, or Integrated Windows Authentication. When IIS authentication is complete, ASP.NET uses the authenticated identity to authorize access. This is the default setting.
Forms Requests that are not authenticated are redirected to an HTML form using HTTP client-side redirection. The user provides his login information and submits the form. If the application authenticates the request, the system issues a form that contains the credentials or a key for reacquiring the identity.
Passport A centralized authentication service provided by Microsoft that offers single login and core profile services for member sites. This mode of authentication was “de-emphasized” (in other words, deprecated) by Microsoft at the end of 2005.
None No authentication mode is in place with this setting.

As you can see, a couple of methods are at your disposal for building an authentication/authorization model for your ASP.NET applications. The next section examines the Windows mode of authentication.

Windows-Based Authentication

Windows-based authentication is handled between the Windows server where the ASP.NET application resides and the client machine. In a Windows-based authentication model, the requests go directly to IIS to provide the authentication process. This type of authentication is quite useful in an intranet environment, where you can let the server deal completely with the authentication process — especially in environments where users are already logged on to a network. In this scenario, you simply grab and utilize the credentials that are already in place for the authorization process.

IIS first takes the user’s credentials from the domain login. If this process fails, IIS displays a pop-up dialog box so the user can enter or re-enter his login information. To set up your ASP.NET application to work with Windows-based authentication, begin by creating some users and groups.

Creating Users

You use aspects of Windows-based authentication to allow specific users who have provided a domain login to access your application or parts of your application. Because it can use this type of authentication, ASP.NET makes working with applications that are deployed in an intranet environment quite easy. If a user has logged on to a local computer as a domain user, she will not need to be authenticated again when accessing a network computer in that domain.

The following steps show you how to create a user. It is important to note that you must have sufficient rights to be authorized to create users on a server. If you are authorized, the steps to create users are as follows:

1. Within Windows 7 or 8, choose Start ⇒ Control Panel ⇒ System and Security ⇒ Administrative Tools ⇒ Computer Management. If you are using Windows Server 2003 or 2008 or 2012, choose Start ⇒ Control Panel ⇒ Administrative Tools ⇒ Computer Management. Either one opens the Computer Management utility. It manages and controls resources on the local web server. You can accomplish many things using this utility, but the focus here is on the creation of users.
2. Expand the System Tools node.
3. Expand the Local Users and Groups node.
4. Select the Users folder. You see something similar to the results shown in Figure 20-2.
5. Right-click the Users folder and select New User. The New User window appears, as shown in Figure 20-3.
6. Give the user a name, password, and description stating that this is a test user. In this example, the user is called Bubbles.
7. Clear the check box that requires the user to change his password at the next login.
8. Click the Create button. Your test user is created and presented in the Users folder of the Computer Management utility, as shown in Figure 20-4.

Now create a page to work with this user.

Authenticating and Authorizing a User

Now create an application that enables the user to enter it. You work with the application’s web.config file to control whicth users are allowed to access the site and which users are not allowed.

Add the section presented in Listing 20-1 to your web.config file.

LISTING 20-1: Denying all users through the web.config file

<system.web>
   <authentication mode="Windows" />
   <authorization>
      <deny users="*" />
   </authorization>
</system.web>

In this example, the web.config file is configuring the application to employ Windows-based authentication using the <authentication> element’s mode attribute. In addition, the <authorization> element is used to define specifics about the users or groups who are permitted access to the application. In this case, the <deny> element specifies that all users (even if they are authenticated) are denied access to the application. Not permitting specific users with the <allow> element does not make much sense, but for this example, leave it as it is. Figure 20-5 shows the results.

FIGURE 20-5

image

Any end user — authenticated or not — who tries to access the site sees a large “Access is denied” statement in her browser window, which is just what you want for those not allowed to access your application!

In most instances, however, you want to allow at least some users to access your application. Use the <allow> element in the web.config file to allow a specific user. Here is the syntax:

<allow users="Domain\Username" />

Listing 20-2 shows how the user is permitted access.

LISTING 20-2: Allowing a single user through the web.config file

<system.web>
   <authentication mode="Windows" />
   <authorization>
      <allow users="Win8Pro-En\Bubbles"/>
      <deny users="*"/>
   </authorization>
</system.web>

Even though all users (even authenticated ones) are denied access through the use of the <deny> element, the definitions defined in the <allow> element take precedence. In this example, a single user — Bubbles — is allowed.

Now, if you are logged on to the client machine as the user Bubbles and run the page in the browser, you get access to the application.

Looking Closely at the <allow> and <deny> Nodes

The <allow> and <deny> nodes enable you to work not only with specific users, but also with groups. The elements support the attributes defined in Table 20-2.

TABLE 20-2

ATTRIBUTE DESCRIPTION
Users Enables you to specify users by their domains and/or names
Roles Enables you to specify access groups that are allowed or denied access
Verbs Enables you to specify the HTTP transmission method that is allowed or denied access

When using any of these attributes, you can specify all users with the use of the asterisk (*):

<allow roles="*" />

In this example, all roles are allowed access to the application. Another symbol you can use with these attributes is the question mark (?), which represents all anonymous users. For example, if you want to block all anonymous users from your application, use the following construction:

<deny users="?" />

When using users, roles, or verbs attributes with the <allow> or <deny> elements, you can specify multiple entries by separating the values with a comma. If you are going to allow more than one user, you can either separate these users into different elements, as shown here:

<allow users="MyDomain\User1" />
<allow users="MyDomain\User2" />

or you can use the following:

<allow users="MyDomain\User1, MyDomain\User2" />

Use the same construction when defining multiple roles and verbs.

Authenticating and Authorizing a Group

You can define groups of individuals allowed or denied access to your application or the application’s resources. Your server can contain a number of different groups, each of which can have any number of users belonging to it. The possibility also exists for a single user to belong to multiple groups. Pull up the Computer Management utility to access the list of the groups defined on the server you are working with. Simply click the Groups folder in the Computer Management utility, and the list of groups appears, as illustrated in Figure 20-6.

FIGURE 20-6

image

Right-click the Groups folder to select New Group. The New Group window appears (see Figure 20-7).

FIGURE 20-7

image

To create a group, give it a name and description; then click the Add button and select the users whom you want to be a part of the group. After a group is created, you can allow it access to your application like this:

<allow roles="MyGroup" />

You can use the roles attribute in either the <allow> or <deny> element to work with a group that you have created or with a specific group that already exists.

Authenticating and Authorizing an HTTP Transmission Method

In addition to authenticating and authorizing specific users or groups of users, you can also authorize or deny requests that come via a specific HTTP transmission protocol. You do so using the verbs attribute in the <allow> and <deny> elements:

<deny verbs="GET, DEBUG" />

In this example, requests that come in using the HTTP GET or HTTP DEBUG protocols are denied access to the site. Possible values for the verbs attribute include POST, GET, HEAD, and DEBUG.

Integrated Windows Authentication

So far, you have been using the default Integrated Windows authentication mode for the authentication/authorization process. This is fine if you are working with an intranet application and each of the clients is using Windows, the only system that the authentication method supports. This system of authentication also requires the client to be using Microsoft’s Internet Explorer for straight-through processing (if you don’t want your end users to be challenged), which might not always be possible.

Integrated Windows authentication was previously known as NTLM or Windows NT Challenge/Response authentication. This authentication model has the client prove its identity by sending a hash of its credentials to the server that is hosting the ASP.NET application. Along with Microsoft’s Active Directory, a client can also use Kerberos if it is using Microsoft’s Internet Explorer.

Basic Authentication

Another option is to use Basic authentication, which also requires a username and password from the client for authentication. The big plus about Basic authentication is that it is part of the HTTP specification and therefore is supported by most browsers. The negative aspect of Basic authentication is that it passes the username and password to the server as clear text, meaning that the username and password are quite visible to prying eyes. For this reason, using Basic authentication along with SSL (Secure Sockets Layer) is important.

If you are using Windows Vista/Windows Server 2008 (IIS 7.0), Windows 7/Windows Server 2008 R2 (IIS 7.5), or Windows 8/Windows Server 2012 (IIS 8.0), finding the option to enable Basic authentication is not easy. Instead, you first have to enable IIS to use Basic authentication by choosing Start ⇒ Control Panel ⇒ Programs ⇒ Programs and Features ⇒ Turn Windows features on or off. From the provided dialog box, navigate to the Internet Information Services section and expand until you arrive at World Wide Web Services ⇒ Security. From here, select the Basic Authentication option and click OK to install. Figure 20-8 shows this option.

FIGURE 20-8

image

After this option is installed, you can then return to the Internet Information Services (IIS) Manager and select the Authentication option in the IIS section for the virtual directory you are focusing on. From there, highlight the Basic Authentication option and select Enable from the Actions pane, as illustrated in Figure 20-9 after enabling this feature.

FIGURE 20-9

image

If you are using IIS 6 (or even an earlier version) to implement Basic authentication for your application, you must pull up IIS and open the Properties window for the website you are working with. Select the Directory Security tab and click the Edit button in the Anonymous Access and Authentication Control box. The Authentication Methods dialog box opens.

Uncheck the Integrated Windows Authentication check box at the bottom and select the Basic Authentication check box above it (see Figure 20-10). When you do, you are warned that this method transmits usernames and passwords as clear text.

FIGURE 20-10

image

End by clicking OK in the dialog box. Now your application uses Basic authentication instead of Integrated Windows authentication.

Digest Authentication

Digest authentication is the final mode you are going to explore in this chapter. This model alleviates the Basic authentication problem of passing the client’s credentials as clear text. Instead, Digest authentication uses an algorithm to encrypt the client’s credentials before they are sent to the application server.

To use Digest authentication, you are required to have a Windows domain controller. One of the main issues that arises with Digest authentication is that it is not supported on all platforms and requires browsers that conform to the HTTP 1.1 specification. Digest authentication, however, not only works well with firewalls, but is also compatible with proxy servers.

If you are using IIS 7 or higher, you need to install Digest Authentication just as you installed Basic Authentication. After you install it, you can find this option and are able to enable it from the Authentication section within the IIS Manager.

For IIS 6 or earlier, you can select Digest authentication as the choice for your application in the same Authentication Methods window — simply select the Digest Authentication check box from the properties window.

Forms-Based Authentication

Forms-based authentication is a popular mode of authenticating users to access an entire application or specific resources within an application. Using it enables you to put the login form directly in the application so that the end user simply enters his username and password into an HTML form contained within the browser itself. One negative aspect of forms-based authentication is that the usernames and passwords are sent as clear text unless you are using SSL.

Implementing forms-based authentication in your web application is easy and relatively straightforward. To begin, you make some modifications to your application’s web.config file, as illustrated in Listing 20-3.

LISTING 20-3: Modifying the web.config file for forms-based authentication

<system.web>
   <authentication mode="Forms">
      <forms name="Wrox" loginUrl="Login.aspx" path="/" />
   </authentication>
   <authorization>
      <deny users="?" />
   </authorization>
</system.web>

You must apply this structure to the web.config file. Using the <authorization> element described earlier, you are denying access to the application to all anonymous users. Only authenticated users are allowed to access any page contained within the application.

If the requestor is not authenticated, what is defined in the <authentication> element is put into action. The value of the mode attribute is set to Forms to employ forms-based authentication for your web application. The next attribute specified is loginUrl, which points to the page that contains the application’s login form. In this example, login.aspx is specified as a value. If the end user trying to access the application is not authenticated, his request is redirected to login.aspx so that the user can be authenticated and authorized to proceed. After valid credentials have been provided, the user is returned to the location in the application where he originally made the request. The final attribute used here is path. It simply specifies the location in which to save the cookie used to persist the authorized user’s access token. In most cases, you want to leave the value as /. Table 20-3 describes each of the possible attributes for the <forms> element.

TABLE 20-3

ATTRIBUTE DESCRIPTION
name This name is assigned to the cookie saved in order to remember the user from request to request. The default value is .ASPXAUTH.
loginUrl Specifies the URL to which the request is redirected for login if no valid authentication cookie is found. The default value is login.aspx.
protection Specifies the amount of protection you want to apply to the authentication cookie. The four available settings are:
  • All: The application uses both data validation and encryption to protect the cookie. This is the default setting.
  • None: Applies no encryption to the cookie.
  • Encryption: The cookie is encrypted but data validation is not performed on it. Cookies used in this manner might be subject to plaintext attacks.
  • Validation: The opposite of the Encryption setting. Data validation is performed, but the cookie is not encrypted.
path Specifies the path for cookies issued by the application. In most cases, you want to use /, which is the default setting.
timeout Specifies the amount of time, in minutes, after which the cookie expires. The default value is 30.
cookieless Specifies whether the forms-based authentication process should use cookies when working with the authentication/authorization process.
defaultUrl Specifies the default URL.
domain Specifies the domain name to be sent with forms authentication cookies.
slidingExpiration Specifies whether to apply a sliding expiration to the cookie. If set to True, the expiration of the cookie is reset with each request made to the server. The default value is False.
enableCrossAppsRedirect Specifies whether to allow for cross-application redirection.
requireSSL Specifies whether a Secure Sockets Layer (SSL) connection is required when transmitting authentication information.

After the web.config file is in place, the next step is to create a typical page for your application that people can access. Listing 20-4 (Default.aspx in the code download for this chapter) presents a simple page.

LISTING 20-4: A simple page

<%@ Page Language="VB" %>
        
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>The Application</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
       Hello World
    </div>
    </form>
</body>
</html>

As you can see, this page simply writes Hello World to the browser. The real power of forms authentication is shown in the login.aspx page presented in Listing 20-5 (Login.aspx in the code download for this chapter).

LISTING 20-5: The login.aspx page

VB

<%@ Page Language="VB" %>
        
<script runat="server">
    Protected Sub Button1_Click(ByVal sender As Object, _
      ByVal e As System.EventArgs)
        If (tbUsername.Text = "Christian" And tbPassword.Text = "Bubbles") Then
            FormsAuthentication.RedirectFromLoginPage(tbUsername.Text, True)
        Else
            Response.Write("Invalid credentials")
        End If
    End Sub
</script>
        
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Login Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        Username<br />
        <asp:TextBox ID="tbUsername" runat="server"></asp:TextBox><br />
        <br />
        Password<br />
        <asp:TextBox ID="tbPassword" runat="server"
         TextMode="Password"></asp:TextBox><br />
        <br />
        <asp:Button ID="Button1" OnClick="Button1_Click" runat="server"
         Text="Submit" />
    </div>
    </form>
</body>
</html>

C#

<%@ Page Language="C#"%>
        
<script runat="server">
   protected void Button1_Click(object sender, EventArgs e)
    {
        if (tbUsername.Text == "Christian" && tbPassword.Text == "Bubbles") {
            FormsAuthentication.RedirectFromLoginPage(tbUsername.Text, true);
        }
        else {
            Response.Write("Invalid credentials");
        }
    }
</script>

Login.aspx has two simple TextBox controls and a Button control that asks the user to submit his username and password. The Button1_Click event uses the RedirectFromLoginPage method of the FormsAuthentication class. This method does exactly what its name implies — it redirects the request from Login.aspx to the original requested resource.

RedirectFromLoginPage takes two arguments. The first is the name of the user, used for cookie authentication purposes. This argument does not actually map to an account name and is used by ASP.NET’s URL authorization capabilities. The second argument specifies whether a durable cookie should be issued. If this is set to True, the end user does not need to log in again to the application from one browser session to the next.

Using the three pages you have constructed, each request for the Default.aspx page from Listing 20-4 causes ASP.NET to check that the proper authentication token is in place. If the proper token is not found, the request is directed to the specified login page (in this example, Login.aspx). Looking at the URL in the browser, you can see that ASP.NET is using a query string value to remember where to return the user after he has been authorized to proceed:

http://localhost:35089/Security/Login.aspx?ReturnUrl=%2fSecurity%2fDefault.aspx

Here, the query string ReturnUrl is used with a value of the folder and page that was the initial request.

Look more closely at the Login.aspx page from Listing 20-5, and note that the values placed in the two textboxes are checked to make sure they abide by a specific username and password. If they do, the RedirectFromLoginPage method is invoked; otherwise, the Response.Write() statement is used. In most cases, you do not want to hardcode a username and password in your code. Many other options exist for checking whether usernames and passwords come from authorized users. Some of the other options follow.

Authenticating against Values Contained in the web.config File

The previous example is not the best approach for dealing with usernames and passwords offered for authentication. Hardcoding them directly into your applications is never a good idea. Take a quick look at storing these values in the web.config file itself.

The <forms> element in the web.config file that you worked with in Listing 20-3 can also take a sub-element. The sub-element, <credentials>, allows you to specify username and password combinations directly in the web.config file. You can choose from a couple of ways to add these values. Listing 20-6 shows the simplest method.

LISTING 20-6: Modifying the web.config file to add username/password values

<system.web>
   <authentication mode="Forms">
      <forms name="Wrox" loginUrl="Login.aspx" path="/">
         <credentials passwordFormat="Clear">
            <user name="Christian" password="Bubbles" />
         </credentials>
      </forms>
   </authentication>
   <authorization>
      <deny users="?" />
   </authorization>
</system.web>

The <credentials> element has been included to add users and their passwords to the configuration file. <credentials> takes a single attribute — passwordFormat. The possible values of passwordFormat are Clear, MD5, and SHA1. The following list describes each of these options:

In the example from Listing 20-6, you use a setting of Clear. This method is not the most secure, but it is used for demonstration purposes. A sub-element of <credentials> is <user>; that is where you define the username and password for the authorized user with the attributes name and password.

The next step is to change the Button1_Click event on the login.aspx page shown earlier, as illustrated in Listing 20-7.

LISTING 20-7: Changing the login.aspx page to work with the web.config file

VB

<%@ Page Language="VB" %>
        
<script runat="server">
    Protected Sub Button1_Click(ByVal sender As Object, _
      ByVal e As System.EventArgs)
        If FormsAuthentication.Authenticate(tbUsername.Text, tbPassword.Text) Then
            FormsAuthentication.RedirectFromLoginPage(tbUsername.Text, True)
        Else
            Response.Write("Invalid credentials")
        End If
    End Sub
</script>

C#

<%@ Page Language="C#"%>
              
<script runat="server">
    protected void Button1_Click(object sender, EventArgs e)
    {
        if (FormsAuthentication.Authenticate(tbUsername.Text, tbPassword.Text)) {
            FormsAuthentication.RedirectFromLoginPage(tbUsername.Text, true);
        }
        else {
            Response.Write("Invalid credentials");
        }
    }
</script>

In this example, you simply use the Authenticate() method to get your ASP.NET page to look at the credentials stored in the web.config file for verification. The Authenticate() method takes two parameters — the username and the password that you are passing in to be checked. If the credential lookup is successful, the RedirectFromLoginPage method is invoked.


NOTE Of course, an even better approach is to store user information in a database for performance and maintenance reasons, as outlined in Chapter 19.

It is best not to store your users’ passwords in the web.config file as clear text, as the preceding example did. Instead, use one of the available hashing capabilities so you can keep the end user’s password out of sight of prying eyes. To do this, simply store the hashed password in the configuration file, as shown in Listing 20-8.

LISTING 20-8: Using encrypted passwords

 <forms name="Wrox" loginUrl="Login.aspx" path="/">
   <credentials passwordFormat="SHA1">
      <user name="Christian" password="58356FB4CAC0B801F011B397F9DFF45ADB863892" />
   </credentials>
 </forms>

Using this kind of construct makes it impossible for even the developer to discover a password, because the clear-text password is never used. The Authenticate() method in the login.aspx page hashes the password using SHA1 (because it is the method specified in the web.config file’s <credentials> node) and compares the two hashes for a match. If a match is found, the user is authorized to proceed.

When using SHA1 or MD5, the only changes you make are in the web.config file and nowhere else. You do not have to make any changes to the login page or to any other page in the application. To store hashed passwords, however, you use the FormsAuthentication.HashPasswordForStoringInConfigFile() method (one of the longer method names in the .NET Framework). You use this method in the following manner:

FormsAuthentication.HashPasswordForStoringInConfigFile(TextBox2.Text, "SHA1")

Authenticating against Values in a Database

Another common way to retrieve username/password combinations is by getting them directly from a datastore of some kind. This enables you, for example, to check the credentials input by a user against values stored in Microsoft’s SQL Server. Listing 20-9 presents exemplary code for this credentials check.

LISTING 20-9: Checking credentials in SQL Server

VB

<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<script runat="server">
    Protected Sub Button1_Click(ByVal sender As Object, _
      ByVal e As System.EventArgs)
       Dim conn As SqlConnection
       Dim cmd As SqlCommand
       Dim cmdString As String = "SELECT [Password] FROM [AccessTable] WHERE" & 
          " (([Username] = @Username) AND ([Password] = @Password))"
       conn = New SqlConnection("Data Source=localhost;Initial " & 
          "Catalog=Northwind;Persist Security Info=True;User ID=sa")
       cmd = New SqlCommand(cmdString, conn)
       cmd.Parameters.Add("@Username", SqlDbType.VarChar, 50)
       cmd.Parameters("@Username").Value = TextBox1.Text
       cmd.Parameters.Add("@Password", SqlDbType.VarChar, 50)
       cmd.Parameters("@Password").Value = TextBox2.Text
       conn.Open()
       Dim myReader As SqlDataReader
       myReader = cmd.ExecuteReader(CommandBehavior.CloseConnection)
       If myReader.Read() Then
          FormsAuthentication.RedirectFromLoginPage(TextBox1.Text, False)
       Else
          Response.Write("Invalid credentials")
       End If
       myReader.Close()
   End Sub
</script>

C#

<%@ Page Language="C#"%>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<script runat="server">
    protected void Button1_Click(object sender, EventArgs e)
    {
       SqlConnection conn;
       SqlCommand cmd;
       string cmdString = @"SELECT [Password] FROM [AccessTable] WHERE
          (([Username] = @Username) AND ([Password] = @Password))";
 
       conn = new SqlConnection(@"Data Source=localhost;Initial 
          Catalog=Northwind;Persist Security Info=True;User ID=sa");
       cmd = new SqlCommand(cmdString, conn);
       cmd.Parameters.Add("@Username", SqlDbType.VarChar, 50);
       cmd.Parameters["@Username"].Value = tbUsername.Text;
       cmd.Parameters.Add("@Password", SqlDbType.VarChar, 50);
       cmd.Parameters["@Password"].Value = tbPassword.Text;
       conn.Open();
       SqlDataReader myReader;
       myReader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
       if (myReader.Read()) {
          FormsAuthentication.RedirectFromLoginPage(tbUsername.Text, false);
       }
       else {
          Response.Write("Invalid credentials");
       }
       myReader.Close();
    }
</script>

Leave everything else from the previous examples the same, except for the Login.aspx page. You can now authenticate usernames and passwords against data stored in SQL Server. In the Button1_Click event, a connection is made to SQL Server. (For security reasons, you should store your connection string in the web.config file.) Two parameters are passed in — the inputs from tbUsername and tbPassword. If a result is returned, the RedirectFromLoginPage() method is invoked.

Using the Login Control with Forms Authentication

You have seen how to use ASP.NET forms authentication with standard ASP.NET server controls, such as simple TextBox and Button controls. You can also use the ASP.NET server controls — such as the Login server control — with your custom-developed forms-authentication framework instead of using other controls. This really shows the power of ASP.NET — you can combine so many pieces to construct the solution you want.

Listing 20-10 shows a modified Login.aspx page using the Login server control.

LISTING 20-10: Using the Login server control on the login.aspx page

VB

<%@ Page Language="VB" %>
        
<script runat="server">
    Protected Sub Login1_Authenticate(ByVal sender As Object, _
      ByVal e As System.Web.UI.WebControls.AuthenticateEventArgs)
        If (Login1.UserName = "Christian" And Login1.Password = "Bubbles") Then
            FormsAuthentication.RedirectFromLoginPage(Login1.UserName,
               Login1.RememberMeSet)
        Else
            Response.Write("Invalid credentials")
        End If
    End Sub
</script>
        
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Login Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Login ID="Login1" runat="server" OnAuthenticate="Login1_Authenticate">
        </asp:Login>
    </div>
    </form>
</body>
</html>

C#

<%@ Page Language="C#" %>
        
<script runat="server">
    protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
    {
        if (Login1.UserName == "Christian" && Login1.Password == "Bubbles") {
            FormsAuthentication.RedirectFromLoginPage(Login1.UserName,
               Login1.RememberMeSet);
        }
        else {
            Response.Write("Invalid credentials");
        }
    }
</script>

Because no Button server control is on the page, you use the Login control’s OnAuthenticate attribute to point to the authentication server-side event — Login1_Authenticate(). The event takes care of the authorization lookup (although the values are hardcoded in this example). The username textbox of the Login control can be accessed via the Login1.UserName declaration, and the password can be accessed using Login1.Password. You use the Login1.RememberMeSet property to specify whether to persist the authentication cookie for the user so that he is remembered on his next visit.

This example is a bit simpler than creating your own login form using TextBox and Button controls. You can give the Login control a predefined look-and-feel that is provided for you. You can also get at the subcontrol properties of the Login control a bit more easily. In the end, which methods you employ in your ASP.NET applications is really up to you.

Looking Closely at the FormsAuthentication Class

As you can tell from the various examples in the forms authentication part of this chapter, a lot of what goes on depends on the FormsAuthentication class. For this reason, you should learn what that class is all about.

FormsAuthentication provides a number of methods and properties that enable you to read and control the authentication cookie as well as other information (such as the return URL of the request). Table 20-4 details some of the methods and properties available in the FormsAuthentictation class.

TABLE 20-4

METHOD/PROPERTY DESCRIPTION
Authenticate This method is used to authenticate credentials that are stored in a configuration file (such as the web.config file).
Decrypt Returns an instance of a valid, encrypted authentication ticket retrieved from an HTTP cookie as an instance of a FormsAuthenticationTicket class.
Encrypt Creates a string that contains a valid encrypted authentication ticket that can be used in an HTTP cookie.
FormsCookieName Returns the name of the cookie for the current application.
FormsCookiePath Returns the cookie path (the location of the cookie) for the current application.
GetAuthCookie Provides an authentication cookie for a specified user.
GetRedirectUrl Returns the URL to which the user is redirected after being authorized by the login page.
HashPasswordFor Storing InConfigFile Creates a hash of a provided string password. This method takes two parameters — one is the password and the other is the type of hash to perform on the string. Possible hash values include SHA1 and MD5.
Initialize Performs an initialization of the FormsAuthentication class by reading the configuration settings in the web.config file, as well as getting the cookies and encryption keys used in the given instance of the application.
RedirectFromLogin Page Performs a redirection of the HTTP request back to the original requested page. This should be performed only after the user has been authorized to proceed.
RenewTicketIfOld Conditionally updates the sliding expiration on a FormsAuthenticationTicket instance.
RequireSSL Specifies whether the cookie should be transported via SSL only (HTTPS).
SetAuthCookie Creates an authentication ticket and attaches it to a cookie that is contained in the outgoing response.
SignOut Removes the authentication ticket.
SlidingExpiration Provides a Boolean value indicating whether sliding expiration is enabled.

AUTHENTICATING SPECIFIC FILES AND FOLDERS

You may not want to require credentials for every page or resource in your application. For example, you might have a public Internet site with pages anyone can access without credentials, although you might have an administration section as part of your application that might require authentication/authorization measures.

URL authorization enables you to use the web.config file to apply the settings you need. Using URL authorization, you can apply any of the authentication measures to only specific files or folders. Listing 20-11 shows an example of locking down a single file.

LISTING 20-11: Applying authorization requirements to a single file

<configuration>
   <system.web>
      <authentication mode="None" />
        
      <!-- The rest of your web.config file settings go here -->
        
   </system.web>
        
   <location path="AdminPage.aspx">
      <system.web>
         <authentication mode="Windows" />
        
         <authorization>
            <allow users="Win8Pro-En\Christian" />
            <deny users="*" />
         </authorization>
      </system.web>
   </location>
</configuration>

This web.config file construction keeps the web application open to the general public while, at the same time, it locks down a single file contained within the application — the AdminPage.aspx page. You accomplish this lockdown through the <location> element. <location> takes a single attribute (path) to specify the resource defined within the <system.web> section of the web.config file.

In the example, the <authentication> and <authorization> elements are used to provide the authentication and authorization details for the AdminPage.aspx page. For this page, Windows authentication is applied, and the only user allowed access is Christian in the Win8Pro-En domain. You can have as many <location> sections in your web.config file as you want.

PROGRAMMATIC AUTHORIZATION

So far, you have seen a lot of authentication examples that simply provide a general authorization to a specific page or folder within the application. Yet, you may want to provide more granular authorization measures for certain items on a page. For example, you might provide a link to a specific document only for users who have an explicit Windows role. Other users may see something else. You also might want additional commentary or information for specified users, while other users see a condensed version of the information. Whatever your reason, this role-based authorization practice is possible in ASP.NET by working with certain objects.

You can use the Page object’s User property, which provides an instance of the IPrincipal object. The User property provides a single method and a single property:

Working with User.Identity

The User.Identity property enables you to work with some specific contextual information about the authorized user. Using the property within your ASP.NET applications enables you to make resource-access decisions based on the information the object provides.

With User.Identity, you can gain access to the user’s name, her authentication type, and whether she is authenticated. Table 20-5 details the properties provided through User.Identity.

TABLE 20-5

ATTRIBUTE DESCRIPTION
AuthenticationType Provides the authentication type of the current user. Example values include Basic, NTLM, Forms, and Passport.
IsAuthenticated Returns a Boolean value specifying whether the user has been authenticated.
Name Provides the username of the user as well as the domain of the user (only if he logged on with a Windows account).

For some examples of working with the User object, take a look at checking the user’s login name. To do this, you use code similar to that shown in Listing 20-12.

LISTING 20-12: Getting the username of the logged-in user

VB

Dim UserName As String
UserName = User.Identity.Name

C#

string userName;
userName = User.Identity.Name;

Another task you can accomplish with the User.Identity object is checking whether the user has been authenticated through your application’s authentication methods, as illustrated in Listing 20-13.

LISTING 20-13: Checking whether the user is authenticated

VB

Dim AuthUser As Boolean
AuthUser = User.Identity.IsAuthenticated

C#

bool authUser;
authUser = User.Identity.IsAuthenticated;

This example provides you with a Boolean value indicating whether the user has been authenticated. You can also use the IsAuthenticated method in an if/then statement, as shown in Listing 20-14.

LISTING 20-14: Using an if/then statement that checks authentication

VB

If (User.Identity.IsAuthenticated) Then
' Do some actions here for authenticated users
Else
' Do other actions here for unauthenticated users
End If

C#

if (User.Identity.IsAuthenticated) {
   // Do some actions here for authenticated users
}
else {
   // Do other actions here for unauthenticated users
}

You can also use the User object to check the authentication type of the user. You do so with the AuthenticationType property, illustrated in Listing 20-15.

LISTING 20-15: Using the AuthenticationType property

VB

Dim AuthType As String
AuthType = User.Identity.AuthenticationType

C#

string authType;
authType = User.Identity.AuthenticationType;

Again, the result usually is Basic, NTLM, or Forms.

Working with User.IsInRole()

If you are using Windows-based authentication, you can check to make sure that an authenticated user is in a specific Windows role. For example, you might want to show specific information only for users in the Subscribers group in the Computer Management Utility. To accomplish that, you can use the User object’s IsInRole method, as shown in Listing 20-16.

LISTING 20-16: Checking whether the user is part of a specific role

VB

If (User.IsInRole("Win8Pro-En\Subscribers")) Then
  ' Private information for subscribers
Else
  ' Public information
End If

C#

if (User.IsInRole("Win8Pro-En\Subscribers")) {
   // Private information for subscribers
}
else {
   // Public information
}

The IsInRole method’s parameter provides a string value that represents the domain and the group (Windows role). In this case, you specify that any user in the Subscribers Windows role from the Win8Pro-En domain is permitted to see some information not available to users who don’t belong to that specific role.

Another possibility is to specify some of the built-in groups available to you. Windows includes a series of built-in accounts such as Administrator, Guest, and User. You can access these built-in accounts in a couple of ways. One is to specify the built-in account with the domain directly:

User.IsInRole("Win8Pro-En\Administrator")

The other possibility is to use the BUILTIN keyword:

User.IsInRole("BUILTIN\Administrator")

Pulling More Information with WindowsIdentity

So far, in working with the user’s identity information, you have used the standard Identity object that is part of ASP.NET by default. If you are working with Windows-based authentication, you also have the option of using the WindowsIdentity object and other objects. To gain access to these richer objects, create a reference to the System.Security.Principal object in your application.

Used in combination with the Identity object from the preceding examples, these additional objects make certain tasks even easier. For example, if you are working with roles, System.Security.Principal provides access to the WindowsBuiltInRole enumeration.

Listing 20-17 is an example of using the WindowsBuiltInRole enumeration.

LISTING 20-17: Using the WindowsBuiltInRole enumeration

VB

Dim AdminUser As Boolean
AdminUser = User.IsInRole(WindowsBuiltInRole.Administrator.ToString())

C#

bool adminUser;
adminUser = User.IsInRole(WindowsBuiltInRole.Administrator.ToString());

Instead of specifying a string value of the domain and the role, you can use the WindowsBuiltInRole enumeration to easily access specific roles on the application server. When working with this and other enumerations, you also have IntelliSense (see Figure 20-11) to help you make your selections easily.

FIGURE 20-11

image

The roles in the WindowsBuiltInRole enumeration include the following:

Using System.Security.Principal, you have access to the WindowsIdentity object, which is much richer than working with the default Identity object. Listing 20-18 lists some of the additional information you can get through the WindowsIdentity object.

LISTING 20-18: Using the WindowsIdentity object

VB

<%@ Page Language="VB" %>
<%@ Import Namespace="System.Security.Principal" %>
        
<script runat="server">
    Protected Sub Page_Load(ByVal sender As Object, _
      ByVal e As System.EventArgs)
        
       Dim AuthUser As WindowsIdentity = WindowsIdentity.GetCurrent()
       Response.Write(AuthUser.AuthenticationType.ToString() & "<br>" &
          AuthUser.ImpersonationLevel.ToString() & "<br>" &
          AuthUser.IsAnonymous.ToString() & "<br>" &
          AuthUser.IsAuthenticated.ToString() & "<br>" &
          AuthUser.IsGuest.ToString() & "<br>" &
          AuthUser.IsSystem.ToString() & "<br>" &
          AuthUser.Name.ToString())
    End Sub
</script>

C#

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Security.Principal" %>
        
<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
       WindowsIdentity AuthUser = WindowsIdentity.GetCurrent();
       Response.Write(AuthUser.AuthenticationType.ToString() + "<br>" +
          AuthUser.ImpersonationLevel.ToString() + "<br>" +
          AuthUser.IsAnonymous.ToString() + "<br>" +
          AuthUser.IsAuthenticated.ToString() + "<br>" +
          AuthUser.IsGuest.ToString() + "<br>" +
          AuthUser.IsSystem.ToString() + "<br>" +
          AuthUser.Name.ToString());
    }
</script>

In this example, an instance of the WindowsIdentity object is created and populated with the current identity of the user accessing the application. Then you have access to a number of properties that are written to the browser using a Response.Write() statement. The displayed listing shows information about the current user’s credentials, such as whether the user is authenticated, anonymous, or running under a guest account or a system account (like the one for the application pool used). It also gives you the user’s authentication type and login name. Figure 20-12 shows a result.

FIGURE 20-12

image

IDENTITY AND IMPERSONATION

By default, ASP.NET runs under an account that has limited privileges. For example, you might find that although the account can gain access to a network, it cannot be authenticated to any other computer on the network.

The account setting is provided in the machine.config file:

<processModel
 enable="true"
 userName="machine"
 password="AutoGenerate" />

These settings force ASP.NET to run under the system account (ASP.NET or Network Service). This is really specified through the userName attribute that contains a value of machine. The other possible value you can have for this attribute is system. Here’s what each entails:

Specifying an account of your choosing using the <processModel> element in either the machine.config or web.config files is also possible:

<processModel
 enable="true"
 userName="MySpecifiedUser"
 password="MyPassword" />

In this example, ASP.NET runs under a specified administrator or user account instead of the default ASP.NET or Network Service account. It inherits all the privileges this account offers. You should consider encrypting this section of the file. Chapter 28 covers encrypting sections of a configuration file.

You can also change how ASP.NET behaves in whatever account it is specified to run under through the <identity> element in the web.config file. The <identity> element in the web.config file allows you to turn on impersonation. Impersonation provides ASP.NET with the capability to run as a process using the privileges of another user for a specific session. In more detail, impersonation allows ASP.NET to run under the account of the entity making the request to the application. To turn on this impersonation capability, you use the impersonate attribute in the <identity> element, as shown here:

<configuration>
   <system.web>
      <identity impersonate="true" />
   </system.web>
</configuration>

By default, the impersonate attribute is set to False. Setting this property to True ensures that ASP.NET runs under the account of the person making the request to the application. If the requestor is an anonymous user, ASP.NET runs under the IUSR_MachineName account. To see this in action, run the example shown in Listing 20-18, but this time with impersonation turned on (True). Instead of getting a username of IIS APPPOOL\DefaultAppPool as the user, you get the name of the user who is requesting the page — Win8Pro-EN\Christian in this example — as shown in Figure 20-13. Of course, this requires that the user has permissions to access the ASP.NET file.

FIGURE 20-13

image

NOTE You may need to use the following web.config setting so that the web server accepts the new impersonation setting:

<system.webServer>
  <validation validateIntegratedModeConfiguration="false" />
</system.webServer>

You also have the option of running ASP.NET under a specified account that you declare using the <identity> element in the web.config file:

<identity impersonate="true" userName="MySpecifiedUser" password="MyPassword"/>

As shown, you can run the ASP.NET process under an account that you specify through the userName and password attributes. These values are stored as clear text in the web.config file.

Look at the root web.config file, and you can see that ASP.NET runs under full trust, meaning that it has some rather high-level capabilities to run and access resources. Here is the setting:

<system.web>
        
    <location allowOverride="true">
        <system.web>
            <securityPolicy>
                <trustLevel name="Full" policyFile="internal"/>
                <trustLevel name="High" policyFile="web_hightrust.config"/>
                <trustLevel name="Medium" policyFile="web_mediumtrust.config"/>
                <trustLevel name="Low" policyFile="web_lowtrust.config"/>
                <trustLevel name="Minimal" policyFile="web_minimaltrust.config"/>
            </securityPolicy>
            <trust level="Full" originUrl=""/>
            <fullTrustAssemblies />
            <partialTrustVisibleAssemblies />
        </system.web>
    </location>
        
</system.web>

Five possible settings exist for the level of trust that you give ASP.NET — Full, High, Medium, Low, and Minimal. You specify the level of trust applied through the <trust> element’s level attribute. By default, it is set to Full. Each one points to a specific configuration file for the policy in which the level can find its trust level settings. The Full setting does not include a policy file because it simply skips all the code access security checks.

SECURING THROUGH IIS

ASP.NET works in conjunction with IIS; not only can you apply security settings directly in ASP.NET (through code or configuration files), but you can also apply additional security measures in IIS itself. IIS enables you to apply the access methods you want by working with users and groups (which were discussed earlier in the chapter), working with restricting IP addresses, file extensions, and more. Security through IIS is deserving of a chapter in itself, but the major topics are explored here.

Working with File Extensions

You can work with many types of files in ASP.NET. These files are defined by their extensions. For example, you know that .aspx is a typical ASP.NET page, and .asmx is an ASP.NET web service file extension. These files are mapped by IIS to the ASP.NET DLL, aspnet_isapi.dll.

If you are working with Windows 7 or higher, you can map file extensions to handlers through the IIS Manager. In this tool, select Handler Mappings in the IIS section. You will find a large list of mappings that have already been provided, as illustrated in Figure 20-14.

FIGURE 20-14

image

By highlighting the first *.aspx option and clicking the Edit button, you see that this extension is mapped to the handler System.Web.UI.PageHandlerFactory, as shown in Figure 20-15.

FIGURE 20-15

image

Clicking the Request Restrictions button provides a window that enables you to select the verbs allowed (as shown in Figure 20-16).

FIGURE 20-16

image

To achieve a similar result in IIS 6.0, pull up the Properties window of your web application in IIS or pull up the default website properties. In a specific web application, you must work from the Directory tab; but if you are working with the Default Web Site Properties window, you can instead use the Home Directory tab. From these tabs, click the Configuration button in the Application Settings box. The Application Configuration window includes a Mappings tab, where the mappings are configured. Highlight .aspx in the list of mappings and click the Edit button. Figure 20-17 shows the result.

FIGURE 20-17

image

In the Executable textbox, you can see that all .aspx pages map to the aspnet_isapi.dll from ASP.NET, and that you can also specify which types of requests are allowed in the application. You can either allow all verbs (for example, GET or POST) or specify which verbs are allowed access to the application.

One important point regarding these mappings is that you do not see .html, .htm, .jpg, or other file extensions such as .txt in the list. Your application will not be passing requests for these files to ASP.NET. That might not be a big deal, but in working through the various security examples in this chapter, you might want to have the same type of security measures applied to these files as to .aspx pages. If, for example, you want all .html pages to be included in the forms authentication model that you require for your ASP.NET application, you must add .html (or whatever file extension you want) to the list. To do so, click the Add button in the Application Configuration window.

In the next window, you can add the ASP.NET DLL to the Executable textbox, and the appropriate file extension and verbs to the list, before adding the mapping to your application’s mapping table. Figure 20-18 illustrates this example.

FIGURE 20-18

image

When dealing with the security of your site, you have to remember all the files that might not be included in the default mapping list and add the ones you think should fall under the same security structure.

Using the IIS 7.x/8 Manager

The tool to make the same site modifications in IIS 7, 7.5, and 8, is the Internet Information Services (IIS) Manager, as shown in Figure 20-19.

FIGURE 20-19

image

After making any changes through this window, you can select the Apply Changes link in the Actions pane, and it will notify you if the changes made have been saved. When you are successful, the changes made are applied to the site’s web.config file.

Using the ASP.NET MMC Snap-In

In older versions of IIS than 7, there was no IIS Manager yet; however the ASP.NET MMC console enables you to edit the web.config and machine.config files using an easy-to-use GUI instead of having to dig through the text of those files yourself to make the necessary changes. You can also modify and change most of the items examined in this book using this window. The plug-in is available on the ASP.NET tab (see Figure 20-20) of your web application running under IIS.

FIGURE 20-20

image

When you make the changes directly in the window, you are also making the hardcoded changes to the actual configuration files.

Click the Edit Configuration button on the ASP.NET tab, and the ASP.NET Configuration Settings window opens. There, you can modify how your forms authentication model works in the GUI without going to the application’s web.config file directly. Figure 20-21 shows an example of working with forms authentication in the GUI.

FIGURE 20-21

image

SUMMARY

This chapter covered some of the foundation items of ASP.NET security and showed you how to apply both authentication and authorization to your web applications. It reviewed some of the various authentication and authorization models at your disposal, such as Basic, Digest, and Windows Integrated Authentication. Other topics included forms-based authentication and how to construct your own forms-based authentication models outside of the ones provided via ASP.NET 4.5 by using the membership and role management capabilities it provides. The chapter also discussed how to use authentication properties within your applications and how to authorize users and groups based on those properties. This chapter also took a look at securing your applications through IIS.