eb services are all about connecting businesses in a standard and secure manner. For a real-life Web service, security is intrinsic to every facet of operation and no party would ever agree to interact with a non-secure Web service. Unfortunately, Web services security is still in its infancy; standards such as WS-I are just emerging and there is no built-in support in the development tools for them. That being said, there are quite a few programming techniques you can use today in .NET 1.1 to secure your Web services, and do so in a way that will ease the transition to future standards and protocols.
Who Needs Security?
You do, and you need to design security into your Web services from the ground up. Toy-like Web services you have seen at development conferences or used in tutorials have no place in today’s business and services. Your Web service needs to authenticate callers, making sure they present a valid identity, and your authentication process should not compromise sensitive information, such as passwords. Once a Web service authenticates an identity, it can use that identity for a number of purposes, such as verifying that a caller is authorized to perform certain operations, or disallowing unauthorized access. Web services can use identities for billing, licensing and auditing, and even for run-time service customization.
.NET Web Services and Security
When you use .NET to build a Web service, you rely on the built-in security support in ASP.NET and Internet Information Services (IIS). While this support makes developing secure ASP.NET Web Forms a breeze, it may require some work to develop and consume secure Web services. The problem is that ASP.NET and IIS security assumes there is a user on the other side of the wire, and that the user can type a user name and password into a dialog. Of course, with Web services there is no user involved, because Web services connect a client (an object) to a remote object (the Web service). This means that client-side developers have to provide your Web service with security credentials either explicitly or implicitly. .NET offers two security options to Web service developers: rely on Windows security or provide custom authentication. This article describes these two options and their different flavors and provides a side-by-side comparison of the security techniques.
Using Windows-based security requires that the calling client application provide the credentials of an account on the server (or on the domain server). As a result, Windows security is most appropriate for intranet applications that use Web services to interact across a well-administered corporate network. This is because typically you have relatively fewer clients in an intranet application than in an Internet application. However, if managing a large number of accounts is acceptable to you, you could use Windows security across the Internet as well, where the number of users of the service can be considerably larger.
To use Windows security, all you need to do is configure the Web service appropriately. Once you configure the Web server to use Windows-based authentication, all calls to all methods on the Web service are authenticated.
To configure your Web service to use Windows-based authentication, you need to set the authentication tag in the Web service configuration file to Windows:
You also need to disable anonymous access to the Web service. In IIS, display the properties of the Web service and select the Directory Security tab. Click the Edit… button to bring up the Authentication Methods dialog box. Clear the Anonymous access check box (see Figure 1).
|Basic Windows Authentication|
Basic authentication is an industry-standard authentication protocol (RFC 1945) that requires the caller to send credentials to the server. When you select the Basic authentication check box, IIS will deny access to incoming requests that do not include security credentials: domain, user name, and password. IIS will reply to requests lacking credentials with HTTP error 401. In the case of a Web page, using Basic authentication will cause the browser to display a dialog asking the user to enter network credentials. In the case of a Web service client, there is no browser, so it is up to the client to provide the credentials programmatically.
Consider, for example, the SecureCalculator Web service, defined as:
If you configure this Web service to rely on Basic authentication, the client needs to provide the account credentials. The client may or may not be a Windows or .NET client. However, if the client is written in .NET, then it can use the built-in support that the client-side wrapper class has for passing network credentials (even when interacting with a non-Windows service that relies on Basic authentication). If you develop a client in .NET, when you add a Web reference to the SecureCalculator Web service, Visual Studio .NET generates a client-side SecureCalculator wrapper class that derives indirectly from WebClientProtocol. WebClientProtocol has a public property called Credentials, of type ICredentials. You’ll find ICredentials in the System.Net namespace (see Listing 1 for the ICredentials definition). By default, the Credentials property is not initialized and is set to null. Before invoking a method on a Web service that uses Basic authentication, the client needs to initialize the Credentials property of the wrapper class with a NetworkCredentials object and then call the method:
The client can also set the PreAuthenticate property of the wrapper class to true. This will cause the wrapper class to send credentials to the server, even when not challenged for them. This is an optimization option that can save the round trip after receiving a 401 error.
It is important to note that Basic authentication sends credentials in clear text, so you should use HTTPS or SSL, especially when using Basic authentication over the Internet. If the credentials are static, that is, they are not a run-time parameter the client retrieves somehow, then the client can encapsulate setting the credentials in the constructor of the wrapper class:
Digest Windows Authentication
If you select to use Digest authentication, IIS will look in the request for the digest credentials and authenticate the user accordingly. There is nothing the Web service developer needs to do; it is up to the client to pass the digested credentials. If the client is a .NET client, it needs to initialize the Credentials property of the wrapper class with an object of type CredentialCache, as defined in Listing 2. CredentialCache is a collection of credentials that is polymorphic with ICredentials.
The client needs to construct individual credentials and add them to the cache, while specifying the type of the credentials using a non-type-safe string. Then, just as when using Basic authentication, the client needs to initialize the Credentials property of the wrapper class with the CredentialCache, optionally enable pre-authentication, and call the method:
With static credentials, the client can also encapsulate these settings in the wrapper class constructor.
Integrated Windows Authentication
If the client’s identity is not adequate and you need to explicitly provide other Windows credentials, then you should use Basic or Digest authentication.
UserManager accesses a database and authenticates the caller against the entries in the database. You will see UserManager used in the custom authentication code samples.
If the callers provide valid credentials, they are allowed to call the Web method; otherwise, the Web service can throw an exception. Custom authentication is appropriate when the callers do not have accounts on the server and when the number of identities is considerably large. An important distinction between custom authentication and Windows authentication is that custom authentication offers a fine level of control over which calls to Web methods the Web service authenticates. For example, a Web method that returns current inventory status is less sensitive than a Web method that processes a purchase order. In the interest of throughput, you may choose to only authenticate purchasing calls.
When using custom authentication, you need to allow anonymous access to the service by selecting the Anonymous access check box in the IIS Authentication Mode dialog box, and by setting the authentication mode in the Web service configuration file to None:
Using custom authentication you can use whatever custom mechanism you want, but in general, you will likely use a log-in method, SOAP headers, or SOAP extensions. Each of these mechanisms has its variations and you can come up with your own tweaks as well.
Listing 3 shows the server-side code for security using the log-in method. The SecureCalculator Web service encapsulates and uses a Session variable to set the value of the Boolean IsAuthenticated property. Note that every Web method that deals with security has to enable session state support explicitly by setting the EnableSession property of the WebMethod attribute to true:
The LogIn() method of the SecureCalculator shown in Listing 3 uses the UserManager helper class to verify that the caller provided valid credentials, and if so, it sets the IsAuthenticated property to true. Before doing anything else, every method must verify that the caller is authenticated by checking the value of IsAuthenticated. The Web server only authenticates the caller the first time the caller connects to the service; subsequent calls are not authenticated. The caller will be logged out once the session ends or when the cookie used to manage the session expires. For increased security you can also provide a LogOut() method that allows the client to explicitly log out.
A .NET client using the log-in method of authentication has to enable support for cookies in the Web service wrapper class by initializing the CookieContainer property, typically by modifying the wrapper class constructor:
The client, of course, needs to call LogIn() before using secure methods of the Web service:
You can take the log-in method approach one step further by factoring it into a base class and adding declarative authentication support using a custom principal. A principal is an object that represents an identity and its role membership. Listing 4 shows the LogInWebService abstract class. Like the LogIn() method in Listing 3, LogInWebService uses a session variable to indicate whether the caller is authenticated and encapsulates the session variable in the IsAuthenticated property. LogInWebService also uses a session variable to store the user name and encapsulates the session variable in the UserName property.
It is always a good practice to encapsulate session variables and expose logical properties around them. This eases the task of maintenance and provides a single place to enforce business rules. For example, UserName only allows the user name to be set if the user is authenticated.
The LogIn() and LogOut() methods of LogInWebService are nearly the same as the methods of the same name shown in Listing 3. One difference is that in LogInWebService, LogIn() stores the user name in the UserName property if the caller is authenticated, while LogOut() resets the user name if the caller logs out. The main difference between the methods, however, is in the LogInWebService constructor. If the caller is authenticated (because it already called the LogIn() method), the constructor creates a generic identity around the user name, and provides it to a generic principal. Then, the constructor installs the generic principal as the principal of the current thread:
Both GenericIdentity and GenericPrincipal are defined in the System.Security.Principal namespace. The purpose of this is clear when you examine a Web service that derives from LogInWebService:
All the SecureCalculator needs to do is add the PrincipalPermission attribute to sensitive methods, and demand that the caller is authenticated. The PrincipalPermission attribute looks for the current principal associated with the current thread, GenericPrincipal in this case, and asks it if the user is authenticated. The GenericPrincipal installed by default returns false, but ours returns true. This makes developing secure Web services trivial, and it provides the benefits of declarative security.
The System.Web.Services.Protocolsnamespace provides support for SOAP headers. To use SOAP headers, you need to derive a class from SoapHeader and add credentials, such as user name and password, as class members. The class members in a SOAP header class must be public and in the form of fields or properties only (see Listing 5). Next, you need to add a member variable to the Web service class of the header type (see AuthHeader in Listing 5). When .NET creates the Web services Description Language (WSDL) associated with the service, the WSDL will contain the appropriate type information about the SOAP header member variable for the use of the clients.
You must decorate any Web method that accesses a SOAP header variable with the SoapHeader attribute, letting .NET know which member variable the method accesses:
.NET will automatically initialize the AuthHeader member with the information provided by the client. All that is left for the Web method to do is to authenticate the caller (using UserManager again) in the sensitive methods. In Listing 5, this is done using the Authenticate() helper method, which simply extracts the credentials from the header member variable.
When a .NET client adds a Web reference to a service that uses SOAP headers, .NET will generate a definition of a client-side SoapHeader derived class, with public variables only (it will convert properties in the original header class). .NET will also add to the wrapper class a matching member variable. The type of that member will be the type of the client-side header class, and the name of the member will be the type name with a Value suffix, for example:
The wrapper class header member variable is set to null by default.
To pass credentials, the client has to initialize the member and call the Web method:
The headers will be forwarded to the Web service, where it will use them to authenticate the caller. If the credentials are static, the client can also encapsulate initialization of the SOAP header in the wrapper class constructor.
Using SOAP headers in this manner requires that the client provide credentials on every method call. Authentication on the server side can be a time-consuming operation. If one-time authentication is sufficient for your needs, you can optimize for throughput and use a session variable to record the fact that the client has already authenticated itself. Listing 6 shows this technique. When asked to authenticate, the Web service checks the value of the IsAuthenticated property, which encapsulates the session variable. If the caller is already authenticated, no further action is required. If not, the service uses UserManager to authenticate the caller.
Note that the client still must provide the credentials in the SOAP header on every call, but the service uses them only during the first call.
You can also use SOAP extensions to transport the caller’s credentials. If you encrypt the payload as well, there is no need for secure channels. Developing a real-life SOAP extension involves a non-trivial amount of work. In practice, the use of SOAP extensions assumes a .NET client interacting with a .NET service, because other development platforms have very little or no support for it.
This article only provides a brief mention of SOAP extensions, as a full discussion of this topic would merit an article in its own right.
Comparing the Options
Table 1: Comparing Windows-based and custom authentication techniques for Web services.
Here are the main points to consider when choosing an authentication mechanism:
My own recommendation is that whenever possible, you should choose Windows authentication over custom authentication to minimize the amount of work involved in developing the Web service. If you do need a custom solution, I would opt for the log-in method factored to a base class. It is trivial to apply, it results in clear declarative security for the Web methods, and it is intuitive and easy for the client to use. In addition, it is easier to call a method than to use a SOAP header or extension, especially if the client platform, such as legacy Visual Basic 6.0, does not have native support for SOAP headers or extensions.
No doubt, support for Web services security will become much more powerful and integrated in future versions of .NET, especially once the standards are finalized and adopted. However, you should not wait for that day to come. Armed with the techniques shown in this article, you can deploy and consume secure .NET Web services now.
Share the Post:
In today’s digital world, technology has changed how we make payments. From contactless cards to mobile wallets, it’s now easier
No matter what you do or where you work, you are likely to encounter Windows software at some point or