Because security is one of the most fundamental aspects in the development and deployment of a Web service, there are a myriad of articles, documentation, and samples of how to make it secure. Yet the majority of this information is conveyed as abstract theory, as opposed to practical, real-world implementation.
Here, I'll share some practical examples on Web service security in .NET, not just abstract theories. These examples provide an easy and fast way to accomplish a rather complex task.
Let's explore programmatic Web service security using Visual Studio .NET to implement a custom, stateful SOAP Header to authenticate a consumer before allowing a method to execute. I will also show you how to remove public access to your Web service, how to prevent anonymous users from obtaining your WSDL file, and how to implement your Web service in an unauthorized manner. I will then explain how you can wrap your entire Web service implementation in a highly secure, encrypted format.
A reusable security pattern
Web services are typically built as an add-in functionality or as an API on top of an existing ASP.NET application. The Web service may interact directly with your database or in an N-tier solution by calling intermediary components to perform the functionality. Your Web service needs to know who is invoking it and what privileges that user has in your application.
More than likely, your users, roles, business rules, and authorizations have already been implemented, and you simply want to tap into this existing information. To do this, your Web service needs to implement a pattern for determining the requesting client's credentials. You could, in theory, pass a user ID and password to every method in your Web service, but a more object-oriented approach is to create a class that extends System.Web.Services.Protocols.SoapHeader. This class will become your SOAP Header, which is passed to your Web service and will include everything needed to authenticate your client.
Listing A shows the server-side implementation code of a simple Web service, SecureWebServiceTester.asmx, and its associated SOAP Header class.
The SecurityContext class extends the SoapHeader class, which tells the .NET Framework that it's a Soap Header and allows you to reference this class in the <WebMethod()> attribute. The main class, SecureWebServiceTester, contains the actual implementation methods for the Web service. These methods are denoted with the <WebMethod()> attribute.
The LoginUser() function takes in an instance of your SecureContext Soap Header class and uses this object as it would any other object. Note that the object name is referenced within quotation marks and is matched to the secureCtx class attribute.
The method then performs the authentication you need by verifying the user ID and password variables contained within the secureCtx object. If the LoginUser() method determines that the user is valid, it generates an authentication ticket that’s inserted into the response's cookies collection.
The HelloWorld() method is an example of a method you would call on the Web service once your client is authenticated. This pattern requires less coding because you need to authenticate only once in a given session and wrap your Web service implementation logic with a simple check, like so:
If Context.User.Identity.IsAuthenticated = True Then...
Once you've implemented the Web service and it's ready to go, you’re ready to code the Web service consumer. The one drawback to this pattern is that the client must have cookies enabled to maintain the security ticket. Typically, the client would be a Web browser, which obviously provides built-in cookie handling, assuming the user has not disabled it. But what if you want your Web service accessible by a desktop Windows application? Consuming Web services from a desktop application provides an incredible amount of power, flexibility, and extensibility. The sample code in Listing B shows how to consume your Web service from a Windows application.
To implement your Web service correctly, you must import System.Net.CookieContainer. This allows you to create an instance of a CookieContainer object, named cookieContainer1, which will store the authentication ticket returned by our LoginUser() method in the Web service.
Private WSObjAs New
Next, you need to create an instance of your Web service itself:
This must be declared globally at the form level because your authentication ticket must maintain state across the various methods implemented on your Windows Form. Within the New() method, which is generated by VS.NET, you set your Web service object's CookieContainer property to your cookieContainer1 object. This tells the Web service object to use your own cookie container object to store any cookies returned by your Web service.
To consume your Web service from another ASP.NET application, you would use nearly identical code with the exception of the manual cookie storage code.
Are you secure?
The code samples in Listings A and B are simple demonstrations of how to programmatically secure your Web services by denying access to any client who does not have a validated authentication ticket. The level of security model complexity should be dictated by your business requirements. If your data sensitivity is high, you should take measures to ensure that your customer data is as secure and private as possible.
Why make a Web service private?
You may ask, "Why remove public access to a Web service?" Many developers view Web services as a public, freely consumable service where any consumer may discover a Web service and implement it in his or her own applications. While this scenario is true, there are many situations where you may not want your Web service publicly available.
For example, a large organization with multiple office locations throughout the country, each with a custom internal solution that communicates with the other offices via Web services may want a private Web service. Or perhaps you're working in an organization that implemented a new cutting-edge system of delivering complex information via Web services, and you want to charge your clients for utilizing this service. In both of these examples, the target audience of your Web service is definitive and must be controlled by the implementer of the Web service, restricting access to those who aren't authorized.
If you require a private Web service, it isn't enough simply to hope that nobody happens to guess what and where your Web service is. Restricting access to authorized consumers won't prevent others from manually discovering your Web service on the Internet; reading its WSDL description; revealing all of its methods, parameters, and return values; and implementing the Web service in their own application—even though they will be denied access due to the SOAP Header security. The fact is, if it exists and is public, it will be found.
The first step in making your Web services private is to put them into their own directory. This is also a good practice for organizational purposes. Once your Web services are in their own directory, you can add a Web.config file that applies to that directory only. Every ASP.NET application generated by Visual Studio .NET automatically has this file inserted at the root of the project and is responsible for the configuration settings of the entire ASP.NET application. The Web.config file contained in the root of the project is the only file that may contain application-specific configurations, because the configurations may be only declared once globally for the entire application. However, every subdirectory in an ASP.NET application may contain its own Web.config file with specific instructions for security, authentication, protocols, etc. If you use Visual Studio .NET, the software will insert a template Web.config file containing all of the XML nodes available in the root Web.config file. However, since this new Web.config file resides in a subdirectory, your project will not execute correctly because many of the definitions are restricted to the root Web.config file and cannot be duplicated.
To make your Web.config file in your Web service subdirectory compile correctly, you must do the following:
- · Locate the <system.web> tag and the closing </system.web> tag.
- · Delete everything in between these two tags with the exception of the <authorization> tag.
The Web.config file is merely an XML file containing specific instructions on how to handle certain characteristics of behavior pertaining to the directory that the Web.config file resides within. Although the <authorization> tag is valid for a Web.config file contained in a subdirectory, you'll need to remove it for your sample in Listing C, because the auto-generated <authorization> tag tells IIS to allow anonymous access to that directory.
You'll also secure your Web service directory by simply adding a few lines of XML to your Web.config file. Listing C shows the entire Web.config file in its complete state.
This solution is incredibly simple yet very powerful. The <protocols> tag instructs IIS which HTTP requests are allowed or not allowed on files contained within the directory. By removing "HttpGet" and "HttpPost" requests, you effectively prevent any direct browser access to your Web services. By removing the "Documentation" protocol, you tell IIS not to display the WSDL description for your Web services.
The interesting part about this is that it doesn't prevent a consumer of your Web service from accessing it. Once your Web service is complete, and you're confident of your interface contract defined by the Web service, you may generate the WSDL file, distribute it to your authorized consumers, and then make your Web service private by adding the lines in Listing C to your Web service directory.