Windows Authentication

Windows authentication is one of the options that ASP.NET gives you for identifying callers. Because Windows authentication maps incoming requests to accounts on the Web server or in the Web server鈥檚 domain, you don鈥檛 use it to generically expose content to all comers over the Internet. Instead, you use it to serve content to a well-defined populace鈥攁 populace that you control through Windows user accounts. Windows authentication on the front end is typically paired with ACL authorization on the back end to control access to the resources that your application exposes. But it works with URL authorization, too.

Recall that Windows authentication comes in four forms: basic, digest, integrated, and certificate. All four forms map incoming requests to accounts on your network, but each does so in a different way. The next several sections describe the inner workings of basic, digest, and integrated Windows authentication and the user experiences that they convey. After that, you鈥檒l put Windows authentication to work in a real ASP.NET application.

Basic Authentication

Basic authentication is an HTTP standard. It鈥檚 documented in RFC 2617, which you can read online at ftp://ftp.isi.edu/in-notes/rfc2617.txt. Basic authentication transmits a user name and password in each request. IIS maps the user name and password to an account on the Web server, producing an access token that can be used to perform ACL-based security checks.

It sounds simple, and it is. To demonstrate how basic authentication works, suppose that your company deploys a series of Web pages containing information that only employees should be able to see. The IT staff places the files in a virtual directory on your Web server and configures IIS to disallow anonymous access to that directory and to require basic authentication. The first time you attempt to retrieve a page from that directory, the Web server returns a 401 status code indicating that authentication is required. It also includes in the response a WWW-Authenticate header identifying the type (or types) of authentication that it accepts. (The details differ slightly when a proxy server is involved, but the principle is valid nonetheless.) Here鈥檚 a portion of a response returned by IIS 5.0 indicating that access to the requested resource requires basic authentication:

HTTP/1.1聽401聽Access聽Denied
Server:聽Microsoft聽IIS-5.0
聽聽.
聽聽.
聽聽.
WWW-Authenticate:聽Basic聽realm="jupiter"

Your browser responds by popping up a dialog box asking for a user name and password (Figure 10-3). It then concatenates the user name and password to a string that identifies the authentication type, base-64-encodes the result, and transmits it to the browser in the Authorization header of an HTTP request. Here鈥檚 the Authorization header transmitted by Internet Explorer 6.0 following a login with the user name 鈥淛eff鈥?and the password 鈥渋mbatman鈥?

Authorization:聽Basic聽SmVmZjppbWJhdG1hbg==

And here are the contents of the base-64-encoded portion of the header after decoding:

Jeff:imbatman

To prevent you from having to log in again and again, the browser includes the same Authorization header in future requests to the same realm. A realm is simply a logical security space that encompasses all or part of a Web site.

Figure 10-3
User name and password dialog displayed by Internet Explorer 6.0.

All authentication mechanisms have pros and cons. Here鈥檚 what鈥檚 good about basic authentication:

And here鈥檚 what鈥檚 bad:

If you use basic authentication and the lines to your Web server aren鈥檛 physically secured, be sure you use it over HTTPS, not HTTP. Otherwise, you鈥檒l secure access to honest (or technically unsophisticated) users but leave yourself vulnerable to attacks by others.

Digest Authentication

Digest authentication is similar to basic authentication. When you attempt to access a resource guarded by digest authentication, the browser solicits a user name and password by popping up a dialog box. The Web server uses the credentials that you enter to assign an identity to the request. The big difference between basic and digest authentication is that digest doesn鈥檛 transmit clear-text passwords. Instead, it passes an authentication token that is cryptographically secure. As a result, you can use it over unencrypted channels without fear of compromising your Web server.

The inner workings of digest authentication are documented in RFC 2617 (ftp://ftp.isi.edu/in-notes/rfc2617.txt). When the client first requests a resource guarded by digest authentication, the server returns a 401 error and includes a 鈥渘once鈥濃攁 string of 1s and 0s鈥攊n a WWW-Authenticate header. The browser responds by prompting for a user name and password. It then transmits the user name back to the server, along with a hash or 鈥渄igest鈥?computed from the combined user name, password, and nonce. The server authenticates the request by performing its own hash on the user name, password, and nonce. The password the server uses doesn鈥檛 come from the client; it comes from the server itself (or from a connected server). If the hashes match, the user is authenticated. Significantly, digest authentication never requires a plain-text password to be transmitted over an HTTP connection. It鈥檚 also compatible with proxy servers.

Digest authentication offers the following advantages:

But it has disadvantages, too:

Because of these restrictions, and because digest authentication doesn鈥檛 support delegation (the ability to make a call from one machine to another and have the call execute as the caller on the remote machine) on Windows 2000 servers, digest authentication is not widely used.

Integrated Windows Authentication

Integrated Windows authentication uses Windows login credentials to authenticate users. Rather than prompt a user for a user name and password and transmit them over HTTP, a browser asked to identify the user through integrated Windows authentication carries on a conversation with the Web server and identifies the user by using that person鈥檚 login identity on the client. In other words, if Bob logs in to his Windows PC, starts Internet Explorer, and requests a resource protected by integrated Windows authentication, a handshake ensues between Internet Explorer and the Web server, and Bob鈥檚 request executes as Bob on the server. Obviously, Bob has to be a valid account on the server (or in a domain that the server can authenticate against) or else access will be denied. Unless configured to do otherwise, the browser asks for a user name and password only if Bob is not a valid account on the server.

Integrated Windows authentication isn鈥檛 an Internet standard; rather, it is a proprietary authentication protocol that permits Windows login credentials to travel over HTTP. Its inner workings haven鈥檛 been fully documented by Microsoft, although some details have been published by third parties. The details vary somewhat depending on the security provider being used, which can be either NTLM or Kerberos. In essence, however, the client and server negotiate a trust in a series of exchanges that involve user names, domain names, nonces, and hashes.

Here are the positives regarding integrated Windows authentication:

And here are the negatives:

Integrated Windows authentication is a great solution for in-house networks that sit behind firewalls and whose browser clients can be carefully controlled鈥攖hat is, restricted to Internet Explorer. It is poorly suited for general Internet use.

Getting Information About Authenticated Users

ASP.NET exposes information about callers via an HttpContext property named User. HttpContext objects accompany each and every request and are exposed to application code through the Context properties of various ASP.NET classes such as Page, WebService, and HttpApplication. Pages (ASPX files) access User through Page.Context.User or simply Page.User.

User鈥檚 type is IPrincipal. IPrincipal is an interface defined in the System.Security.Principal namespace. It鈥檚 implemented by the WindowsPrincipal and GenericPrincipal classes. When a user is authenticated using Windows authentication, Page.User refers to a WindowsPrincipal object. When a user is authenticated using another form of authentication (for example, forms authentication), Page.User refers to a GenericPrincipal object. IPrincipal has a method named IsInRole that you can use to test role memberships. (For users authenticated using Windows authentication, roles correspond to groups. For users authenticated using forms authentication, roles do not exist unless they鈥檙e programmatically assigned. You鈥檒l learn how to use role-based security with forms authentication near the end of this chapter.) IPrincipal also has a property named Identity that exposes information regarding an authenticated user鈥檚 identity. Identity is actually a reference to an IIdentity interface. IIdentity has the following members:

Property

Description

AuthenticationType

Reveals which form of authentication was used

IsAuthenticated

Reveals whether the user is authenticated

Name

Reveals an authenticated user鈥檚 name

All this sounds confusing, but in practice, User makes getting information about callers trivially easy. If you want to find out whether a caller is authenticated from code in an ASPX file, do this:

if聽(User.Identity.IsAuthenticated)聽{
聽聽聽聽//聽The聽caller聽is聽authenticated
}

You can also find out whether a caller is authenticated by checking the Request object鈥檚 IsAuthenticated property, which under the hood consults User.Identity.IsAuthenticated. If you want to know the caller鈥檚 name (assuming the caller is authenticated), do this:

string聽name聽=聽User.Identity.Name;

For a user authenticated using Windows authentication, the name is of the form domainname\username, where domainname is the name of the domain in which the user is registered (or the machine name if the account is a local account instead of a domain account), and username is the user鈥檚 name. For forms authentication, the name is normally the one that the user typed into a login form. One use for the user name is to personalize pages for individual users. The application in the next section demonstrates how.

Windows Authentication in Action

The application shown in Figure 10-6, which I鈥檒l refer to as 鈥淐orpNet鈥?since it models a simple intranet-type application, uses Windows authentication and ACL authorization to restrict access to some of its pages and to personalize content for individual users. It contains three pages:

You鈥檒l deploy CorpNet such that anyone in the company can view General.aspx but only selected individuals can view Salaries.aspx and Bonuses.aspx.

Before testing can begin, you need to deploy the application on your Web server and configure it to provide the desired level of security. Here are the steps:

  1. Create a directory named Basic somewhere鈥攁nywhere鈥攐n your Web server.

  2. Use the IIS configuration manager to transform Basic into a virtual directory named 鈥淏asic.鈥?/p>

  3. While in the IIS configuration manager, configure Basic to require basic authentication and to disallow anonymous access. How? Right-click Basic in the IIS configuration manager and select Properties from the ensuing context menu. Go to the Directory Security page of the property sheet that pops up and click the Edit button under 鈥淎nonymous access and authentication control.鈥?In the ensuing dialog box, uncheck 鈥淎nonymous access鈥?and check 鈥淏asic authentication,鈥?as shown in Figure 10-4. OK the changes, and then close the configuration manager.

  4. Create two user accounts on your Web server for testing purposes. Name the accounts 鈥淏ob鈥?and 鈥淎lice.鈥?It doesn鈥檛 matter what passwords you assign, only that Bob and Alice are valid accounts on the server.

  5. Copy General.aspx, Salaries.aspx, Bonuses.aspx, Bonuses.xml, and Web.config to the Basic directory.

  6. Change the permissions on Salaries.aspx so that only Bob and the ASPNET account are allowed access. At the very least, grant them read permission. Other permissions are optional.

  7. Change the permissions on Bonuses.xml (not Bonuses.aspx) to grant access to Everyone but specifically deny access to Alice.

    Figure 10-4
    Configuring a directory to require basic authentication.

Now give the application a try by performing the following exercises:

  1. Type http://localhost/basic/general.aspx into your browser鈥檚 address bar to call up General.aspx. Because the Basic directory requires callers to be authenticated using basic authentication, a dialog box will pop up. Enter Bob鈥檚 user name and password. When General.aspx appears, observe that it knows your login name. (See Figure 10-5.)

  2. Restart your browser and repeat the previous exercise, but this time enter Alice鈥檚 user name and password rather than Bob鈥檚. General.aspx still displays just fine because both Bob and Alice have permission to access it.

    Figure 10-5
    General.aspx showing the caller鈥檚 identity.
  3. Without restarting your browser, call up Salaries.aspx. Because you鈥檙e logged in as Alice and Alice isn鈥檛 allowed to read Salaries.aspx, the server reports that access is denied. (If you鈥檙e using Internet Explorer, you may have to type Alice鈥檚 user name and password a few times before being told access is denied.)

  4. Restart your browser and try again to call up Salaries.aspx. This time, log in as Bob when prompted for a user name and password. Because Bob is permitted to read Salaries.aspx, the page comes up and Bob鈥檚 salary appears. Salaries.aspx is capable of displaying salaries for other employees, too, but it uses the caller鈥檚 login name to personalize the information that it displays.

  5. Without restarting your browser, call up Bonuses.aspx. A list of employee bonuses appears.

  6. Restart your browser and call up Bonuses.aspx again. This time, log in as Alice. What do you think will happen? Anyone can access Bonuses.aspx, but Bonuses.aspx calls DataSet.ReadXml to read Bonuses.xml, and Alice isn鈥檛 permitted to read Bonuses.xml. You鈥檙e logged in as Alice. Will Bonuses.aspx report an error?

As you can see, Bonuses.aspx comes up just fine and even shows a list of bonuses. Clearly Bonuses.aspx succeeded in reading Bonuses.xml. How can that happen if Alice lacks permission to read Bonuses.xml? The answer is simple, but also subtle.

IIS tagged the request with Alice鈥檚 access token, and it passed that access token to ASP.NET. ASP.NET knows that the caller is Alice and won鈥檛 allow Alice to retrieve an ASPX file (or any other ASP.NET file type) for which Alice lacks access permission. But because Web.config lacks a statement enabling impersonation, any code executed inside the request executes as ASPNET, not as Alice. ASPNET has permission to read Bonuses.xml, so Alice wasn鈥檛 prevented from viewing employee bonuses.

I purposely laid this trap for you to drive home an important point. ASP.NET performs ACL checks on ASPX files and other ASP.NET file types using the caller鈥檚 identity, regardless of whether impersonation is enabled. That means you can prevent any caller from retrieving an ASPX file simply by denying that caller permission to read the file. However, if a caller pulls up an ASPX file and the ASPX file programmatically reads another file, you must tell ASP.NET to impersonate the caller if you want the read to be subject to an ACL check using the caller鈥檚 identity.

You can prevent Alice from seeing the data in Bonuses.xml by modifying Web.config to read as follows. The new line is highlighted in bold:

<configuration>
聽聽<system.web>
聽聽聽聽<authentication聽mode="Windows" />
聽聽聽聽<identity聽impersonate="true" />
聽聽</system.web>
</configuration>

After making the change to Web.config, restart your browser, log in as Alice, and try to view Bonuses.aspx again. This time, you鈥檙e greeted with an error message reporting that an error occurred while processing the page. That message is displayed by the exception handler in Bonuses.aspx鈥檚 Page_Load method, which catches the XmlException thrown when ReadXml can鈥檛 read Bonuses.xml. Restart your browser and log in as Bob, however, and you can once again view Bonuses.aspx.

CorpNet demonstrates several important principles that you should keep in mind when writing ASP.NET applications that use Windows authentication:

Remember, too, that directories containing ASPX files and other ASP.NET files must grant read permission to the account that Aspnet_wp.exe runs as (ASPNET by default) or else ASP.NET itself can鈥檛 access resources in those directories. To prove it, temporarily deny ASPNET permission to read from the Basic directory. Now even Bob can鈥檛 view Salaries.aspx.

General.aspx
<%@聽Page聽Language="C#" %>

<html>
聽聽<body>
聽聽聽聽<h1>Welcome聽to聽CorpNet!</h1>
聽聽聽聽<hr>
Welcome聽to聽the聽corporate聽intranet!聽We聽don't聽have聽a聽lot聽to聽offer
right聽now,聽but聽check聽back聽in聽a聽few聽days聽and聽we'll聽have聽information
regarding聽the聽massive聽layoff聽that聽has聽been聽the聽subject聽of聽so聽many
rumors.聽Do聽remember,聽though,聽that聽we're聽watching聽you聽all聽the聽time.
We聽even聽know聽who聽you聽are聽because聽you聽had聽to聽provide聽a聽user聽name
and聽password聽to聽see聽this聽page.聽To聽prove聽it,聽your聽user聽name聽is
shown聽below.<br>
聽聽聽聽<h3>
聽聽聽聽聽聽<%
聽聽聽聽聽聽聽聽if聽(User.Identity.IsAuthenticated)
聽聽聽聽聽聽聽聽聽聽聽聽Response.Write聽(User.Identity.Name);
聽聽聽聽聽聽%>
聽聽聽聽</h3>
聽聽</body>
</html>
Figure 10-6
CorpNet source code.
Salaries.aspx
<%@聽Page聽Language="C#" %>

<html>
聽聽<body>
聽聽聽聽<h1>Salaries</h1>
聽聽聽聽<hr>
聽聽聽聽<%
聽聽聽聽聽聽if聽(!User.Identity.IsAuthenticated)
聽聽聽聽聽聽聽聽聽聽Response.Write聽("Sorry,聽but聽no聽salary聽information " +
聽聽聽聽聽聽聽聽聽聽聽聽聽 "is聽available聽for聽unauthenticated聽users.");
聽聽聽聽聽聽else聽{
聽聽聽聽聽聽聽聽聽聽if聽(User.Identity.Name.IndexOf聽("Jeff")聽!=聽-1)
聽聽聽聽聽聽聽聽聽聽聽聽聽聽Response.Write聽("Jeff's聽salary聽is聽$10,000.");
聽聽聽聽聽聽聽聽聽聽else聽if聽(User.Identity.Name.IndexOf聽("John")聽!=聽-1)
聽聽聽聽聽聽聽聽聽聽聽聽聽聽Response.Write聽("John's聽salary聽is聽$20,000.");
聽聽聽聽聽聽聽聽聽聽else聽if聽(User.Identity.Name.IndexOf聽("Bob")聽!=聽-1)
聽聽聽聽聽聽聽聽聽聽聽聽聽聽Response.Write聽("Bob's聽salary聽is聽$30,000.");
聽聽聽聽聽聽聽聽聽聽else聽if聽(User.Identity.Name.IndexOf聽("Alice")聽!=聽-1)
聽聽聽聽聽聽聽聽聽聽聽聽聽聽Response.Write聽("Alice's聽salary聽is聽$40,000.");
聽聽聽聽聽聽聽聽聽聽else聽if聽(User.Identity.Name.IndexOf聽("Mary")聽!=聽-1)
聽聽聽聽聽聽聽聽聽聽聽聽聽聽Response.Write聽("Mary's聽salary聽is聽$50,000.");
聽聽聽聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽聽聽聽聽聽Response.Write聽("No聽salary聽information聽is " +
聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽 "available聽for " +聽User.Identity.Name);
聽聽聽聽聽聽}
聽聽聽聽%>
聽聽</body>
</html>
Bonuses.aspx
<%@聽Import聽Namespace="System.Data" %>

<html>
聽聽<body>
聽聽聽聽<asp:DataGrid聽ID="MyDataGrid" Width="40%" RunAt="server" />
聽聽聽聽<asp:Label聽ID="Output" RunAt="server" />
聽聽</body>
</html>

<script聽language="C#" runat="server">
聽聽void聽Page_Load聽(Object聽sender,聽EventArgs聽e)
聽聽{
聽聽聽聽聽聽try聽{
聽聽聽聽聽聽聽聽聽聽DataSet聽ds聽=聽new聽DataSet聽();
聽聽聽聽聽聽聽聽聽聽ds.ReadXml聽(Server.MapPath聽("Bonuses.xml"));
聽聽聽聽聽聽聽聽聽聽MyDataGrid.DataSource聽=聽ds;
聽聽聽聽聽聽聽聽聽聽MyDataGrid.DataBind聽();
聽聽聽聽聽聽}
聽聽聽聽聽聽catch聽(Exception)聽{
聽聽聽聽聽聽聽聽聽聽Output.Text聽= "An聽error聽occurred聽processing聽this聽page.";
聽聽聽聽聽聽}
聽聽}
</script>
Bonuses.xml
<?xml聽version="1.0" encoding="UTF-8"?>
<Bonuses>
聽聽<Bonus>
聽聽聽聽<Name>Jeff</Name>
聽聽聽聽<Amount>1000</Amount>
聽聽</Bonus>
聽聽<Bonus>
聽聽聽聽<Name>John</Name>
聽聽聽聽<Amount>2000</Amount>
聽聽</Bonus>
聽聽<Bonus>
聽聽聽聽<Name>Bob</Name>
聽聽聽聽<Amount>3000</Amount>
聽聽</Bonus>
聽聽<Bonus>
聽聽聽聽<Name>Alice</Name>
聽聽聽聽<Amount>4000</Amount>
聽聽</Bonus>
聽聽<Bonus>
聽聽聽聽<Name>Mary</Name>
聽聽聽聽<Amount>5000</Amount>
聽聽</Bonus>
</Bonuses>
Web.config
<configuration>
聽聽<system.web>
聽聽聽聽<authentication聽mode="Windows" />
聽聽</system.web>
</configuration>
Windows Authentication and URL Authorizations

CorpNet currently uses ACL authorizations to restrict access to its pages. But ASP.NET also supports URL authorizations. To demonstrate, create a subdirectory named Secret in the Basic directory and move Salaries.aspx, Bonuses.aspx, and Bonuses.xml into it. Then place the following Web.config file in the Secret directory (and be sure to replace domainname with the appropriate machine name or domain name for the Bob account):

<configuration>
聽聽<system.web>
聽聽聽聽<authorization>
聽聽聽聽聽聽<allow聽users="domainname\Bob" />
聽聽聽聽聽聽<deny聽users="*" />
聽聽聽聽</authorization>
聽聽</system.web>
</configuration>

Log in as Bob and you鈥檒l be able to access Salaries.aspx and Bonuses.aspx just fine. But log in as anyone else, and it鈥檒l be as if the files don鈥檛 exist.

The chief drawback to URL authorizations is that they only protect files registered to ASP.NET. You can鈥檛 use them to protect ordinary HTML files, for example. Another limitation is that URL authorizations are based on stringified names rather than Windows security IDs (SIDs). For these reasons, ACL authorizations are typically used in lieu of URL authorizations when Windows authentication is used too.

Windows Authentication and Role-Based Security

Role-based security is a powerful concept in Web applications. Rather than restrict access to callers based on user names, role-based security restricts access based on 鈥渞oles鈥濃擟EO, manager, developer, clerk, or whatever鈥攖hat those users belong to. If you modify the permissions on the Secret directory to allow access only to members of a group named Managers, for example, you鈥檙e exercising role-based security. Only users that belong to that group can call up Salaries.aspx and Bonuses.aspx.

Role-based security can also be applied using URL authorizations. The following Web.config file restricts access to the host directory to members of the Managers group. Behind the scenes, ASP.NET handles the chore of mapping the groups to which the caller belongs to roles named in allow and deny elements:

<configuration>
聽聽<system.web>
聽聽聽聽<authorization>
聽聽聽聽聽聽<allow聽roles="domainname\Managers" />
聽聽聽聽聽聽<deny聽users="*" />
聽聽聽聽</authorization>
聽聽</system.web>
</configuration>

Role-based security applied through URL authorizations suffers from the same limitations as user-based security applied through URL authorizations and is therefore rarely used outside forms authentication.