Implementing NTLM Authentication for Your ASP.NET Web Services
By Dan Appleman
Published: 6/7/2004
Reader Level: Intermediate
Rated: 4.40 by 5 member(s).
Tell a Friend
Rate this Article
Printable Version
Discuss in the Forums

Download the Source

Anyone who uses the Web is familiar with the two common forms of authentication. The most common one is "form-based" authentication, in which you enter some sort of user ID and password on a form. The Web application then authenticates you based on some internal scheme, and typically uses a cookie to keep track of the authentication status throughout the session. A less common method is to use one of the authentication schemes internal to Internet Information Server (IIS): Basic, Digest, Kerberos, or NTLM. You know this kind of authentication is in use because your browser pops up a dialog box requesting a user name and password before serving up the requested page. This type of authentication works against the user list on the target server or domain. The common wisdom is that Web services can't or shouldn't raise user interfaces for authentication purposes (or any other), and thus most articles on Web services focus on how to get the authentication information to the Web service, not how it gets to the client in the first place.

Web services are used in many different ways. They allow applications to take advantage of functionality published by Web sites. They allow implementation of multi-tier applications. And in some architectures, the assumption that it is not possible or advisable to prompt the user for a password simply isn't true.

When we developed the Desaware Licensing System for .NET, we implemented the activation server as an ASP.NET Web service. This has proven to be a good choice, in that it is not only easy to use, but easy to deploy — especially on shared hosts that are normally reluctant to host custom software (or charge a premium to do so), but have no problem with custom ASP.NET software because of its inherent stability and improved security model. The Licensing System exposes two objects through a Web service: an activation object that is public (in the sense of not requiring any authentication), and a management object that is intended to be used to configure the licensing system (and thus requires some form of authentication). ASP.NET is ideal for this type of architecture, because IIS can set the authentication for each file independently.

The initial release of the Licensing System provided two types of authentication: Windows integrated authentication for local systems or use with VPN access, and IP-based authentication. The integrated authentication was originally limited to using the default credentials of the license manager application (in other words, the credentials of the user running the management program), but for the latest release, we wanted to implement a model similar to the familiar browser model where users are prompted for a user name password on those sites that require it.

Disabling Anonymous Access to a Web Page or Web Service

The first step in turning on Windows authentication is to select the individual Web service pages that you wish to secure (you can, of course, secure an entire Web site in this manner), as shown in FIGURE 1.

FIGURE 1: Selecting the page(s) to secure

When you bring up the properties for the selected page, you can choose the File Security tab and edit the Anonymous access and authentication control for the page, as shown in FIGURE 2.

FIGURE 2: Editing the File Security settings

Turn off Anonymous access for the page, and select the type of authentication you wish to use, as shown in FIGURE 3. It's best to avoid Basic authentication unless you're accessing the Web service via SSL (in which case the channel encryption will protect the information).

FIGURE 3: The Authentication Methods dialog box

In this case integrated Windows authentication is a fairly safe choice because the client application is itself a .NET application, and thus running on Windows with full access to the .NET Framework. In most cases you'll use NTLM, which is fairly secure even on a regular http channel, but you can also use Kerberos. Both systems allow authentication using a password without actually sending the password across the connection. Instead, they transfer information that is encrypted with the password, a process that will only work if both the client and server agree on the password.

Assigning Credentials to a Web Service Proxy

When you add a Web reference to a .NET application, Visual Studio builds a hidden Web proxy class for the Web service. The Web proxy class is in the file Reference.vb in the Web References subdirectory corresponding to the service. This class, which inherits from the System.Web.Services.Protocols.SoapHttpClientProtocol class, provides a strongly typed object with members that correspond to those of the Web service. For example, if the Web service has a method named "Test", the proxy class will have a method named Test, which when called invokes the Web service method using the proxy class Invoke method.

In this example, the secured Web service is called Service2ws.Service2, and it has one function, "Test" that retrieves a string. You create the proxy object as you would any other object — using the New statement:

Dim mywsproxy As New Service2ws.Service2

The proxy object by default has a URL for the Web service, and a Credentials property that contains a set of credentials that will be sent with the request. You can create a new credential and assign it to the Credentials property of the proxy as follows:

Dim mycredentials As New CredentialCache
Dim m_cred As NetworkCredential
m_cred = New NetworkCredential(txtUser.Text, _
txtPassword.Text, txtDomain.Text)
mycredentials.Add(New Uri(mywsproxy.Url), _
credform.AuthenticationType, m_cred)
mywsproxy.Credentials = mycredentials

Simple enough, but all that demonstrates is how to set up the authentication for the Web service call. Let's take a step back and look at the entire scenario.

The Client Scenario

Before implementing the client security, you need to put some thought into what kinds of authentication the service will support. In the case of our licensing system, we wanted to offer as much flexibility as possible. Some people might secure the service using IP filters — not a bad practice in cases where you have a dedicated IP range for in-house systems. Others might want to authenticate against the user who is running the client — a good choice where clients have VPN access into the internal network, or must be authenticated against a domain to access the service. In both these cases we didn't want to request a user ID and password — the client was already authorized. For this reason, we first try to access the Web service directly, only using the default credentials of the user. You can set a Web proxy to use the current user credentials by setting the Credentials property as follows:

m_Service.Credentials = CredentialCache.DefaultCredentials

The sample code for this article doesn’t do this because it is designed to run on the same system as the Web service, and if the default credentials were set the client would always authenticate, so you wouldn’t have a chance to see the full sequence.

The NetworkCredential object is obtained using a form that has text boxes for the user name, domain, and password. It also has a combo box that allows you to select the type of credential to create. The authentication type is one of the following strings: Basic, Digest, Kerberos, or NTLM (case matters):

Private m_cred As NetworkCredential

Public ReadOnly Property NewCredentials() As NetworkCredential
   Get
      Return m_cred
   End Get
End Property

Public ReadOnly Property AuthenticationType() As String
   Get
      Return cmbAuthentication.Text
   End Get
End Property

Private Sub cmdCancel_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles cmdCancel.Click
   m_cred = Nothing
   Me.Close()
End Sub

Private Sub cmdOk_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles cmdOk.Click
   Try
      m_cred = New NetworkCredential(txtUser.Text, _
      txtPassword.Text, txtDomain.Text)
      Me.Close()
   Catch ex As Exception
      MsgBox(ex.Message)
   End Try
End Sub

Private Sub frmcredentials_Load(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles MyBase.Load
   cmbAuthentication.SelectedIndex = 0
End Sub

The actual sequence for invoking the method is as follows:

  1. Try to access the method using the default credentials.
  2. If a WebException occurs, check to see if it is a protocol error that contains a 401 (unauthorized access) error message.
  3. If so, bring up the credentials form and prompt the user for the required information.
  4. Retry the operation using the new credentials.

The actual code is as follows:

Private Sub cmdTest2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles cmdTest2.Click

   ListBox1.Items.Clear()

   Dim mywsproxy As New Service2ws.Service2

   Try
      ListBox1.Items.Add(mywsproxy.Test())
   Catch webex As WebException
      ListBox1.Items.Add("Exception: " & webex.Message)
      If webex.Status = Net.WebExceptionStatus.ProtocolError _
         AndAlso webex.Message.IndexOf("401") > 0 Then
         Dim mycredentials As New CredentialCache
         Dim credform As New frmcredentials
         ListBox1.Items.Add("Prompting user for credentials")
         credform.ShowDialog()
         If credform.NewCredentials Is Nothing Then
            ListBox1.Items.Add("This service requires authentication")
            Exit Sub
         End If
         mycredentials.Add(New Uri(mywsproxy.Url), _
         credform.AuthenticationType, credform.NewCredentials)
         mywsproxy.Credentials = mycredentials
         Try
            ListBox1.Items.Add(mywsproxy.Test())
         Catch ex As Exception
            ListBox1.Items.Add(ex.Message)
         End Try
      End If
   Catch ex As Exception
      ListBox1.Items.Add("Exception: " & ex.Message)
   End Try

End Sub

 

Obviously, this is a simple example. In a real application that makes ongoing use of the Web service, you would continue to reuse the proxy (and the credentials), so you wouldn't prompt the user for information on each Web request.

Conclusion

This example demonstrates one answer to a critical (and often unasked) question: Where does the authentication information live when it's not being used? If you stop with the assumption that Web services should not prompt the user for this information, there's an implication that authentication information must be stored somewhere, perhaps on the client machine. This is a workable solution, if the machine itself is secure. But what if it's a laptop? Considering the rate of laptop theft, keeping this information on the machine is questionable (to say the least). In cases where you are implementing a client-server architecture, where the client application is closely associated with the Web service (as compared to a Web service designed for public or general developer consumption), prompting the user for the information might be exactly what you want to do.



Marketplace
(Sponsored Links)
What are the green links?
   



 
Copyright © 2007 CMP Tech LLC |
Privacy Policy (4/10/06) | Your California Privacy Rights (4/10/06) | Terms of Service | Advertising Info | About Us | Help