Authentication in ASP.NET


The default behavior of an ASP.NET Web application or Web service is to ignore user identity and simply operate under the ASPNET account. For many purposes this is fine, particularly when the Web service is intended for public consumption. However, sometimes you need to know who people are before you perform certain tasks. It is even possible to force ASP.NET to impersonate a user—any tasks performed are performed under the account of that user, with that user’s privileges.

The easiest way to get access to authentication features via ASP.NET is to use those already built in to IIS. You can let IIS authenticate users, and then you can pass this information to your Web application.

The other alternatives in ASP.NET are to use Forms, Microsoft Passport, or custom authentication. Of these, only custom authentication really applies to Web services because Forms and Passport both rely on more of a Web Forms front end. Forms authentication requires you to provide a login form and a list of accounts to authenticate against as part of the web.config file for the application. Passport is currently in a state of flux, but possibilities are emerging. Currently, the signup process for a passport authenticated Web application involves information such as Web pages to redirect to once authenticated, where to find corporate files such as licensing agreements, and so on, none of which are applicable to Web services. It is, however, possible to implement a system whereby a passport authenticated Web site is used to obtain a Kerberos ticket to be passed with a Web service request, but we won’t cover this here because of possible imminent changes in Passport functionality. Also, if you do use such an approach, you won’t actually be using Passport to authenticate Web service users—you will be implementing custom authentication.

To control the authentication method used by an ASP.NET application, including Web service applications, you can modify the <authentication> element in web.config:

<authentication mode="authenticationMode" /> 

The value of authenticationMode can be

  • None No authentication will be performed by ASP.NET. Choose this option when you implement your own custom authentication method.

  • Windows IIS authentication will be used. To configure this further, you must configure the application through IIS management, as discussed in the next section.

  • Forms ASP.NET Forms-based authentication will be used. As discussed above, this isn’t appropriate for Web services.

  • Passport Passport authentication will be used. Again, as things stand, this isn’t applicable to Web services.

IIS Authentication

IIS can authenticate users in a number of ways:

  • Anonymous access No authentication is performed.

  • Basic authentication Authentication is in the form of a plain text username and password combination sent by the client.

  • Digest authentication A challenge/response mechanism whereby a hash of the user’s password rather than plain text is transmitted.

  • Integrated A Windows-specific challenge/response mechanism that uses hashes. Similar to digest authentication.

  • Client certificates Users are identified by a digitally signed key.

We’ll look at each of these authentication methods in the sections that follow, with the exception of client certificates, which we’ll look at in Chapter 15. First, though, some general information. When a user is authenticated by IIS, a System.Security.Principal.WindowsPrincipal object is created that represents the user. This object supports the IPrincipal interface, which is made accessible to the Web service through the request context information, via Context.User. Basic information concerning the user is available via the IPrincipal.Identity property, which is an IIdentity interface. For users authenticated via IIS, it is possible to cast the Context.User and Context.User.Identity references into WindowsPrincipal and WindowsIdentity objects, respectively, where Windows-specific implementations can be manipulated.

In the following sections, we’ll use some example code to illustrate how these authentication methods work and show basic use of the user information via the classes outlined above. This code is contained in the AuthenticationMethods solution in the sample code. This solution contains a client Windows Forms application called AuthenticationClient, which passes authentication details to one of two Web services, also included in the solution. The Web services are contained in the projects IISAuthenticationWebServices and CustomAuthenticationWebServices, and are both called AuthenticatedService. We’ll examine these services and the client that calls them as necessary.

Anonymous Access

The simplest authentication instruction to pass to IIS is “don’t bother authenticating users.” This is useful when we don’t need authentication, and it is the behavior you’ve seen up to this point in the book. To enable anonymous access (or reenable it, since it is enabled by default) we must modify the properties for the Web service application in IIS Manager. To do this, open the IIS application’s Properties dialog box, click on the Directory Security tab, and click the Edit button in the Anonymous Access And Authentication Control group. Clicking the Edit button will bring up the Authentication Methods dialog box shown in Figure 11-1.

click to expand
Figure 11-1: Configuring authentication methods in IIS Manager

The AuthenticatedService service in IISAuthenticationWebServices attempts to obtain information about the authenticated user, which the service returns in an array of five strings. The first three of these strings are extracted from the WindowsIdentity object for the user as follows:

[WebMethod] public string[] GetAuthenticationDetails() {     string[] returnValues = new string[5];     returnValues[0] = Context.User.Identity.AuthenticationType;     returnValues[1] = Context.User.Identity.Name;     returnValues[2] = Context.User.Identity.IsAuthenticated.ToString();

Even though we are using anonymous access, this object still exists—it just won’t be associated with a user (as you’ll see shortly).

Next the service attempts to read from and write to a file using the user account for the logged in user. Unless we modify the access permissions for the files and directories concerned, this file access will fail for anonymous authentication, causing the Authentication Client application to display the screen shown in Figure 11-2.

click to expand
Figure 11-2: The Authentication Client application accessing a Web service using anonymous authentication

Note that the Authentication Type and User Name fields are empty strings, whereas the IsAuthenticated field is False, denoting that the user hasn’t been authenticated.

Basic Authentication

You can select Basic authentication from the same properties window in IIS Manager where you select anonymous access. When you do this, a warning appears, pointing out that Basic authentication can be a security risk in unsecured communications. Later in the chapter, we’ll look at secure communications in more detail, but for now we’re just looking at how authentication works, so we can ignore this warning.

Once the IISAuthenticationWebServices application is configured for Basic authentication, we can try the Web service again, via the Windows client. This time the results will depend on what permissions the selected user has. For a local user or administrator, file access will probably be permitted. For a domain user with no local rights, file access will be restricted. Of course, you can change this by assigning different ACL permissions to your C:\Temp directory. For the user selected in Figure 11-3, TOL\Karli, we’ve assigned both read and write permissions to the directory.

The main bonus of Basic authentication is that it is a feature of the HTTP 1.0 specification, which means it is available to the widest variety of client operating systems. However, the security risk is that it involves plain text, as mentioned above, and without some form of encryption this information might be vulnerable to Internet spying.

click to expand
Figure 11-3: The Authentication Client application accessing a Web service using Basic authentication

Before moving on, it’s worth looking at how the client application sends the authentication information to the service. The code for this is as follows:

private void CallIISAuthenticatedWebService() {     try     {         IISAuthenticationWebServices.AuthenticatedService service =              new IISAuthenticationWebServices.AuthenticatedService();         NetworkCredential credential =              new NetworkCredential(userNameBox.Text, passwordBox.Text,                                    domainBox.Text);         service.Credentials = credential;         fillResults(service.GetAuthenticationDetails());     }     catch (Exception ex)     {         MessageBox.Show("Access denied! Exception details: "                          + ex.Message, "Access Denied",                             MessageBoxButtons.OK, MessageBoxIcon.Error);     } }

We use a System.Net.NetworkCredential object, which we assign to the Credentials property of the instance of the AuthenticatedService Web service. The proxy class uses this information to populate the required HTTP headers for Basic authentication.

This same code can be used for Digest and Integrated authentication.

Digest Authentication

Digest authentication is similar to Basic authentication but doesn’t directly expose password information. However, even though snoopers cannot discover the information, it is still vulnerable to attack if the communications channel isn’t secured—the hashed information sent to IIS can be obtained and copied in such a way that other people can impersonate an authorized user. Nevertheless, Digest authentication provides better security than Basic authentication.

To use Digest authentication, you must be using an Active Directory–administered domain because it works only with Active Directory user accounts. User accounts must also be Digest enabled. This involves selecting the Store Password Using Reversible Encryption option, which you can do through the Active Directory Users and Computers MMC snap-in, and resetting the passwords.

Once configured, though, Digest authentication operates in much the same way as Basic authentication. Figure 11-4 shows the result using Digest authentication.

click to expand
Figure 11-4: The Authentication Client application accessing a Web service using Digest authentication

The only difference here as far as we’re concerned is that the Authentication Type shows Digest. However, this makes little difference to the code we write to deal with user accounts in the rest of our Web service.

Functionally, the problem with this authentication method is that it is supported only by Microsoft Internet Explorer 5.0 and later, and it doesn’t tie into the Kerberos security system. Of course, we aren’t using a Web browser front end, so this isn’t a problem for Web services.

Integrated Authentication

Integrated authentication is a great choice for intranet environments and can be very secure. It does have its problems, though. For a start, this type of authentication is difficult to use through a firewall or proxy. (It is possible, but it might involve an additional security risk.) It is usable with Internet Explorer 2.0 and later via a Web site or a Web service interface, and it works with Kerberos for Internet Explorer 5.0 and later.

The results using this type of authentication are shown in Figure 11-5.

click to expand
Figure 11-5: The Authentication Client application accessing a Web service using Integrated authentication

Again, the results are similar, apart from the change of Authentication Type to Negotiate. Using the Negotiate HTTP header to let the client know what to do is one of two modes that Integrated authentication can use. The other, for less sophisticated clients, is NTLM challenge/response mode. The results are the same, but Kerberos isn’t used.

Custom Authentication

Custom authentication can mean many things because you can implement it in innumerable ways. The main reason for using this type of authentication is that you don’t have to rely on Windows user accounts; this can be advantageous when you don’t want to assign every user of your Web service such an account. Instead, you can use a table of users stored in a database somewhere, perhaps specific to an application that the Web service is a part of. For example, let’s say you have an e-commerce application, and users place orders using a Windows application front end. You might make use of user information stored in a customer database to obtain information when users browse catalog listings or place orders with a credit card.

We’ll look at one scheme that has become quite popular and would be appropriate to the scenario just outlined—exchanging authentication information via SOAP headers.

The situation is as follows:

  1. The user obtains a security token via an authorizing Web service.

  2. The user uses this token in a custom SOAP header in requests to a separate Web service.

  3. The token either expires after a set period, or it can be forced to expire when the user logs out.

The advantage to this approach is that only the authorizing Web service needs to be accessed securely if we are careful. One way to be careful, which is used in the next example, is to assemble the token using the IP address used by the user, so that even if the token is stolen it cannot easily be used to spoof the user account.

The code in the CustomAuthenticationWebServices project has a single Web method in Logon.asmx called GetToken to illustrate step 1:

[WebMethod] public string GetToken(string userName, string password) {     // validate username / password combination.     string userIP = Context.Request.UserHostAddress;     Guid newToken = Guid.NewGuid();     OleDbConnection conn =         new OleDbConnection(@"Provider=Microsoft.Jet.OLEDB.4.0;" +         @"User ID=Admin;Data Source=C:\Inetpub\wwwroot\wscr\11\" +         @"CustomAuthenticationWebServices\bin\WSCRLogons.mdb;");     OleDbCommand cmd = conn.CreateCommand();     cmd.CommandText = "INSERT INTO Logons (Token, IP, Created, UserName)" +         " VALUES ('" + newToken.ToString() + "', '" + userIP + "', '" +         DateTime.Now.ToUniversalTime() + "', '" + userName + "')";     conn.Open();     cmd.ExecuteNonQuery();     conn.Close();     return newToken.ToString(); }

This method doesn’t actually validate the username/password combination, although this would be simple to add, but it does generate and store a token. It does this in a simple database table called Logons in the file WSCRLogons.mdb, which is structured as follows:

Column

Type

Token

Text (primary key)

IP

Text

Created

Date/Time

UserName

Text

The token obtained is a Guid value and is passed back to the client. This token can then be used in a custom SOAP header called TokenHeader:

public class TokenHeader : SoapHeader {     public string Token;     public TokenHeader()     {     } }

As you saw in Chapter 2, we associate this header with our Web method by adding a public field of the header type (type TokenHeader in this case) to the other Web service, AuthenticatedService.asmx, and using the SoapHeader attribute on the Web method that uses the header:

public TokenHeader tokenHeader; [WebMethod] [SoapHeader("tokenHeader", Direction=SoapHeaderDirection.In)] public string[] GetAuthenticationDetails() {     string userName = CheckLogin();     string[] returnValues = new string[5];     returnValues[0] = "Custom";     returnValues[1] = userName;     returnValues[2] = "True";     returnValues[3] = "N/A";     returnValues[4] = "N/A";     return returnValues; }

Note that we don’t bother with the file access checking that we mentioned (but didn’t look at in detail) earlier. This is because we are not attempting to match up the custom user information with a Windows account, so it makes no sense to worry about it.

The important functionality here is in CheckLogin:

private string CheckLogin() {     if (tokenHeader == null)     {         throw new Exception("SOAP header tokenHeader not supplied.");     }     string userIP = Context.Request.UserHostAddress;     OleDbConnection conn = new OleDbConnection(         "Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;" +         @"Data Source=C:\Inetpub\wwwroot\wscr\11\" +         @"CustomAuthenticationWebServices\bin\WSCRLogons.mdb;");     OleDbCommand cmd = conn.CreateCommand();     cmd.CommandText = "SELECT Token, IP, Created, UserName " +         "FROM Logons WHERE Token = '" + tokenHeader.Token + "'";     conn.Open();     OleDbDataReader reader = cmd.ExecuteReader(         CommandBehavior.CloseConnection);     if (!reader.Read())     {         reader.Close();         throw new Exception("Not logged in.");     }     if ((string)reader["IP"] != userIP)     {         reader.Close();         throw new Exception("Not logged in.");     }     if ((DateTime)reader["Created"] > DateTime.Now.AddHours(1))     {         reader.Close();         throw new Exception("Login expired.");     }     return (string)reader["UserName"]; }

This method attempts to get data from the database entry with the specified GUID. If no such entry exists, an exception is thrown. If the data does exist but the IP used is different for this request, an exception is thrown. If the token is valid but a set amount of time has elapsed since its creation (one hour in this code), an exception is thrown. Otherwise, the method returns the username of the user.

Note that a number of things are not implemented in this example. First, no provision is made for validating the username and password, as noted earlier. Also, no tokens are ever removed from the database if they are invalid, although this isn’t necessarily a problem. Finally, it might be nice to use a SOAP extension class to do the job of CheckLogin, thus avoiding having to call the method from every Web method that requires a token. You can even implement this in such a way that you create a custom user information object that supports the IIdentity interface, which would be available to code in the Web service.

Anyway, back to our client. The code that uses this custom authenticated Web service follows. First we use Logon.asmx to obtain a token:

private void CallCustomAuthenticatedWebService() {     try     {         CustomAuthenticationLogon.Logon logonService =             new CustomAuthenticationLogon.Logon();         string token = logonService.GetToken(userNameBox.Text,             passwordBox.Text);

Next we use this token to create a TokenHeader object for passing to the Web service via a SOAP header:

        CustomAuthenticationWebServices.AuthenticatedService mainService =             new CustomAuthenticationWebServices.AuthenticatedService();         CustomAuthenticationWebServices.TokenHeader header =             new CustomAuthenticationWebServices.TokenHeader();         header.Token = token;         mainService.TokenHeaderValue = header;

Then we call the Web method we’re interested in:

        fillResults(mainService.GetAuthenticationDetails());     }     catch (System.Net.WebException ex)     {         MessageBox.Show("Access denied! Exception details: " +              ex.Message, "Access Denied", MessageBoxButtons.OK,              MessageBoxIcon.Error);     } }

Typically, we need to call the GetToken Web method of Logon.asmx only once. When we have a token, we don’t need to get another one, at least until we log out or the token expires. Also, it’s probably a good idea to store the proxy object for AuthenticatedService.asmx somewhere because once we set the TokenHeader header, there is no need to do it again—it will be passed in all subsequent Web method calls.

Before moving on, it’s worth pointing out the criticisms that some people have with this type of authentication. Perhaps the main problem is that it doesn’t follow the “self-describing” guidelines for Web services. Without knowing how the scheme works, it would be impossible to use. Still, this hardly seems that important because you’d need to be an authorized user in the first place, in which case you’d probably be using a client application that was aware of this functionality. The other concern is that this involves more than one roundtrip to access a Web method, and method calls involve sending a token with each request. Still, it is only one extra call, and not a whole lot of extra data is sent in each request. With the performance we are accustomed to on the Internet, this isn’t that much of a problem.

Now for security concerns. Obviously, the first logon request must be secure, to avoid letting slip any private details. However, there is no need to pass subsequent requests over a secure channel, so only Logon.asmx needs to be secured. Of course, as we will discuss later in this chapter, this scheme doesn’t prevent data returned by AuthenticatedService.asmx from being exposed, but there are ways around this.

Manually Logging On to a Windows Account

Unfortunately, there is no simple way to take a username and a password and use them to log on to a Windows account. To log on to a Windows account, we are forced to use the Win32 API via Interop. However, this will mean that we can hook up the custom authentication sample from the last section to Windows user accounts. The following code, from Win32LogonUserWrapper.cs, exposes the Win32 API method we need (LogonUser) via a static method:

using System; using System.Runtime.InteropServices; using System.Security.Principal; namespace NotAShop.WSCR.Chapter11.CustomAuthenticationWebServices {     public enum LogonType     {         Interactive = 2,         Network = 3,         Batch = 4,         Service = 5,         Unlock = 7,         NetworkCleartext = 8,         NewCredentials = 9     }     public enum LogonProvider     {         Default = 0,         WindowsNT35 = 1,         WindowsNT4 = 2,         WindowsNT5 = 3     }     public class Win32LogonUserWrapper     {         [DllImport("advapi32.dll")]         private static extern bool LogonUser(String lpszUsername, String             lpszDomain, String lpszPassword, LogonType dwLogonType,             LogonProvider dwLogonProvider, out IntPtr phToken);         private Win32LogonUserWrapper()         {         }         public static WindowsPrincipal GetPrincipal(string userName,             string password)         {             IntPtr token;             if (!LogonUser(userName, "TOL", password, LogonType.Network,                 LogonProvider.Default, out token))             {                 return null;             }             WindowsIdentity identity = new WindowsIdentity(token);             return new WindowsPrincipal(identity);         }     } }

Here the domain name is hardcoded, but we could just as easily take it from a Web method parameter. The key thing is that the code returns either a WindowsPrincipal object for the user or null. We can integrate this into the Logon.asmx Web service with little difficulty:

[WebMethod] public string GetToken(string userName, string password) {     if (Win32LogonUserWrapper.GetPrincipal(userName, password) == null)         throw new System.Net.WebException("Invalid credentials.");     string userIP = Context.Request.UserHostAddress;      

We can also store the token retrieved by the LogonUser function so that we can re-create the WindowsPrincipal object in calls to AuthenticatedService.asmx and have access to the user account there.




Programming Microsoft. NET XML Web Services
Programming MicrosoftВ® .NET XML Web Services (Pro-Developer)
ISBN: 0735619123
EAN: 2147483647
Year: 2005
Pages: 172

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