Introduction
This article shows you how to secure a web service using a User Name and password. This article also shows you how to secure the password with a Password digest and thwart man-in-the-middle and replay attacks. The User Name and password must be known before hand to both parties (Server and Client).
Background
In this fast changing world of the web, Security is of paramount importance. There was no particular standard for Web Service security (WS-Security) until April 2004, and Web service developers had to rely on the transport layer to provide security (via SSL/TLS from HTTP) or develop their own custom security mechanism sacrificing interoperability in the process. The whole idea of developing web services is interoperability across all platforms. In April 2004, WS-Security was established as an approved OASIS open standard. Web Service Enhancements (WSE) 2.0 is the first release of the software from Microsoft implementing this approved standard version of WS-Security. This means that applications built with WSE 2.0 security features will interoperate with other Web service platforms as their WS-Security-compliant implementations become available.
Downloading and Installing WSE 2.0
You can download Web Services Enhancements 2.0 from the Microsoft Download Center. After download, choose the Visual Studio Developer option for installation. This will install complete support for Visual Studio .NET 2003. Now, this version of the WSE installation does not embed the WSE docs into Visual Studio itself. It puts it in a separate link in the Start > Programs menu under the Microsoft WSE 2.0 folder. Maybe a future version of the Installation will embed the docs right into Visual Studio where you can see them.
You will need to restart ASP.NET (aspnet_wp.exe or w3wp.exe) at a minimum for WSE 2.0 changes to take effect. It is recommended you restart IIS on your Web Server.
Configuring a Web service project to use WSE 2.0
The .NET assemblies that provide the WSE functionality are contained in the Micorosoft.Web.Services2
namespace. Before using the classes from this namespace, we first have to add a reference to this assembly. We can do this by hand and then make the necessary configuration changes that are outlined in the WSE documentation, or we can make the job a lot easier by using the WSE Visual Studio add-in. The add-in is accessible by right-clicking the Visual Studio project, and then selecting the WSE Settings 2.0... option on the context menu.
There are two check boxes shown on the General tab. The first check box enables use of the current project with WSE. This means that the Microsoft.Web.Services2.dll will be added to the project references and that changes will be made to the web.config file to add support for the WSE configuration handler. In addition, any Web References that are created from this point on will include WSE 2.0 support in the proxy classes generated.
The second check box is only enabled if this is an ASP.NET Web Service project. By selecting it, the WSE SoapExtension is added to the project, which will enable the additional protocol support to work within the ASP.NET Web service HTTP handler (for .ASMX files). This is accomplished by modifying the web.config file and adding the WSE SoapExtension to the list of .asmx SOAP Extensions for the virtual directory.
In the sample code supplied with this article, we have enabled the Visual Studio Web service project, the Visual Studio Class Library project, and the console application project for WSE support, by checking the appropriate boxes on this tab.
Code Sample
Let us now create a small 'Hello' Web Service and secure it via User Name and Password. Our sample code consists of 3 parts.
- The secure Hello Web service. SecureWS.dll
- The client code to access the Web Service. SecureWSClient.dll
- The security code. WSESecurity.dll
The secure Hello Web Service returns a string "Hello Authenticated user <name>" if the Username and password match, and throws a System.Web.Services.Protocols.SoapHeaderException
otherwise.
The Secure Web Service
Fire up Visual Studio .NET and create an ASP.NET Web Service project. Name the project "SecureWS". Enable WSE support for this Web Service project by right clicking on the Project in Solution Explorer and selecting the WSE Setting 2.0... option on the context menu. Place a check mark in both checkboxes and click OK.
Add the following using
statements to Service1.asmx.cs. (Rest assured, even though you are adding Microsoft specific libraries to the code, these libraries implement an open standard and will interoperate with other platforms.)
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Security.Tokens;
Add the following code to Service1.asmx.cs:
[WebMethod]
public string Hello(string name)
{
SoapContext ctxt = RequestSoapContext.Current;
if (ctxt == null)
{
return "Please format the request as a SOAP request and try again.";
}
foreach(SecurityToken tok in ctxt.Security.Tokens)
{
if (tok is UsernameToken)
{
UsernameToken user = (UsernameToken)tok;
return "Hello Authenticated user " + user.Username;
}
}
return "Hello Liar";
}
In the above code, we iterate through each security header since a single SOAP message may contain many headers. We make sure that it has at least one UsernameToken
kind of security header.
The Web Service Client Code
Fire up a new instance of Visual Studio .NET (you can do an Add Project to the existing solution. But don't forget to set the startup project to the correct project.) and create a Console Application. Name the project "SecureWSClient". Enable WSE support for this Console Application project by right clicking on the Project in Solution Explorer and selecting the WSE Setting 2.0... option on the context menu. Place a check mark in the first checkbox and click OK. (The second checkbox will be grayed out since this is not an ASP.NET project.)
Add a Web Reference to the project and provide the URL of the Web Service as "http://localhost/SecureWS/Service1.asmx">http:
. Rename the Web reference to SampleWSE.
Add the following using
statements to the Console application.
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Security.Tokens;
Replace the Main
method in the Console application with the following code:
[STAThread]
static void Main(string[] args)
{
Console.Write("Enter Name: ");
string name = Console.ReadLine();
Console.Write("Enter Password: ");
string password = Console.ReadLine();
SampleWSE.Service1Wse proxy = new SecureWSClient.SampleWSE.Service1Wse();
proxy.RequestSoapContext.Security.Tokens.Add(new UsernameToken(name,
password, PasswordOption.SendHashed));
proxy.RequestSoapContext.Security.Timestamp.TtlInSeconds = 300;
Console.WriteLine(proxy.Hello(name));
Console.WriteLine("Hit enter to end.");
Console.ReadLine();
}
Since we enabled WSE support for this console application, Visual Studio generates a proxy which contains an additional object with the name Service1Wse
. If there was no WSE support enabled, we would have just had the standard Service1
object.
In the above code, an UsernameToken
is sent as part of the SOAP header. The password is transmitted as a hash using the SHA1 hashing algorithm. This ensures confidentiality and thwarts man-in-the-middle style of attacks. The Password digest (hash) is automatically computed using the following formula.
Password_Digest = Base64( SHA1( nonce + created + password ) )
We also set an expiration time for the UsernameToken
to thwart against replay attacks. We give a 5 minute validity here because of differences in the clocks of the client and the server. If the Client and Server clocks were synchronized, we could have given a much lesser validity period - just enough time for the SOAP message to reach the Server. In reality, a nonce (is a randomly generated value) is used and the WSE would invalidate the SOAP message if it detects duplicate nonce value within the same session. We should make sure that the Session timeout maintained by WSE on the Web Server is greater than the Expiration time given by the client or, in other words, the client should give an Expiration time (TtlInSeconds
value) less than that of the WSE Session timeout on the Web Server. For further reading, refer to the OASIS UsernameToken Profile specification.
The Security Code
The default implementation of WSE UsernameToken
will try to authenticate incoming credentials against Windows User accounts on the Web Server. But it is not always possible to create local Windows accounts for every user of your Web service. For example, you may want to validate the user against a User name and password stored in your database and not as a Windows User. For this, we need to do something extra.
Fire up yet another instance of Visual Studio .NET and create a Class library project. Name the project WSESecurity. Add WSE support to this library by right clicking on the Project in Solution Explorer and selecting the WSE Setting 2.0... option on the context menu. Place a check mark in the first checkbox and click OK. Rename Class1.cs to CustomAuthenticator.cs and replace the contents with the following code:
using System;
using System.Security.Permissions;
using Microsoft.Web.Services2.Security.Tokens;
namespace WSESecurity
{
[SecurityPermissionAttribute(SecurityAction.Demand,
Flags=SecurityPermissionFlag.UnmanagedCode)]
public class CustomAuthenticator : UsernameTokenManager
{
protected override string AuthenticateToken(UsernameToken token)
{
if (token == null)
throw new ArgumentNullException();
if (token.Username == "vamsi")
return "mypassword";
else
return null;
}
}
}
This object is never exposed to the outside world but is used internally by WSE to authenticate incoming UsernameToken
s within SOAP messages. As you can see, the CustomAuthenticator
class inherits from UsernameTokenManager
and overrides the AuthenticateToken
method. This method should return a plain text password.
You will need to deploy the WSESecurity.dll to the GAC or just copy it to the bin folder of the Web Service. (Same folder where you have SecureWS.dll.)
Now, we need to plug this class into the WSE pipeline by adding the following code to the web.config file of the Web Service:
<microsoft.web.services2>
<security>
<securityTokenManager qname="wsse:UsernameToken"
type="WSESecurity.CustomAuthenticator, WSESecurity"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-wssecurity-secext-1.0.xsd"/>
</security>
</microsoft.web.services2>
The above code must be added to the <configuration>
section of your web.config file. Also, note that there already maybe a section marked <microsoft.web.services2>
(Visual Studio .NET automatically adds this section at the bottom of the web.config file) in which case, you will just need to add the <security>
section within the existing <microsoft.web.services2>
section.
Conclusion
The Web Service Enhancements 2.0 library provides simple and powerful methods to manage message level security in Web Services. The user authentication module can be updated without recompiling the Web Service itself by making changes to the WSESecurity
library and redeploying it in the GAC or just copying it into the bin folder of the Web Service. The web service code need not worry about security and can concentrate on business logic.