Using Web Services Enhancements (WSE) for Username / Password Authentication
By Peter A. Bromberg, Ph.D.
Printer - Friendly Version
Peter Bromberg

The WS-Security specification , Addendum and related Web Services work is arguably the most important advancement to Web Services since the formalization of the SOAP specification. There is no question that SOAP and XML Web Services has completely changed the business landscape in terms of interoperability and standards.



However, until recently, there have been some gaping deficiencies in the web services problem domain, namely, the ability to handle security, authentication, encryption, digital signature, routing and attachments at the message level, rather than using out of band or OS-proprietary techniques. Because of these deficiencies, many organizations that otherwise would have totally embraced the web services interoperability concept and business model early on have been holding back. There just wasn't a standardized way of doing these important things that everybody could agree to in the same spirit that everyone agreed to the SOAP specification for web services.

With the finalization of the WS-Security specification, vendors can get down to the business of providing interfaces and programming toolkits for their products that all "speak the same language", and developers can begin to use the benefits of these efforts. Microsoft, after a period of testing and use of its initial technology release "WSDK", has now released Web Services Enhancements 1.0 for Microsoft .NET (WSE), its first supported toolset for implementing security within the SOAP message. As of December 9, 2002, you can begin using a supported toolkit for WS-Security. You can download it HERE.

Now a SOAP message, on its own, can be authenticated, its integrity verified, and it can be encrypted in whole or in part using the mechanisms defined in WS-Security. The only thing really missing from the Microsoft implementation is support for Security Assertion Markup Language (SAML), but it does appear to be working toward some implementation of a portion of the SAML architecture for .NET Server. Developers are of course free to implement SAML themselves. The only real deficiency that remains is the fact that WSDL does not yet have the ability to describe WS-Security interfaces for clients that wish to consume a WS-Security - compliant web service. Hopefully, this "missing link" will arrive soon.

The architectural model of WSE is built on a "pipeline" of filters that process inbound and outbound SOAP messages. This is built on the pre-existing SOAPExtension classes, and developers who have experimented with SOAP Extensions for compression, encryption, logging and other purposes will find the implementation familiar.

One of the biggest impediments to learning to use WSE is the fact that the MS documentation and accompanying articles are sometimes difficult to understand, even for advanced developers. In addition, there are only a few samples implemented by authors at the time of this writing, and the ones I've seen are very much NOT "user friendly". I've determined to put an end to this by providing my own sample implementation that will take you through the process of implementing username/password authentication with the WSE. You'll be taken step-by-step through the process and the de-mystification of WSE will be rendered complete with a downloadable solution that will give you everything you need to be up and running with a working example in no time. Understanding and using WSE is not that intimidating; you just have to take it "a slice at a time". So, let's get started!

WSE provides a Microsoft.Web.Services.SoapContext class which gives us an interface that allows us to process the WS-Security SOAP headers and other headers on incoming SOAP messages, and to add WS-Security and other headers for outbound SOAP messages. A wrapper class adds the SOAPContext (just like HttpContext) for the SOAP request and response, and the server uses a SOAPExtension, "Microsoft.Web.Services.WebServicesExtension" which enables us to validate incoming SOAP messages and provides the request and response SoapContext which we can access from within our WebMethods.

Setting up the WSE Environment

To set up the basic WSE environment, we need to configure our ASP.NET application to use the WSE SOAP Extension. The simplest way to do this is to add the required /configuration/system.web/webServices/soapExtensionTypes/Add element to the web.config for your service's virtual directory. The element should look like the following:

<webServices>
<soapExtensionTypes>
<add type="Microsoft.Web.Services.WebServicesExtension, Microsoft.Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1" group="0" />
</soapExtensionTypes>
</webServices>

 

Note that the type attribute must appear on a single line, it's been wrapped here for readability. As far as the WSE is concerned, we are ready to start using it. There are a few additional web.config items that I'll cover shortly. At this point, we only need to add a reference to the Microsoft.Web.Services.dll in the project. That's normally located at:

C:\Program Files\Microsoft WSE\v1.0.2312\Microsoft.Web.Services.dll

In order to validate a user/password in our web service using WSE, we need to understand what WSE offers us in this regard. WS-Security defines a UsernameToken element to provide the means for basic username/password validation. There are three variations, and the last two are the ones we are interested in:

<!-- Clear Text Password -->
<UsernameToken>
<Username>Bob</Username>
<Password Type="wsse:PasswordText">Abracadabra</Password>
</UsernameToken>

<!-- Digest: SHA1 hash of base64-encoded Password -->
<UsernameToken>
<Username>user1</Username>
<Password Type="wsse:PasswordDigest">
QSMAKo67+vzYnU9TcMSqOFXy14U=
</Password>
</UsernameToken>

 

The IPasswordProvider Interface

WSE defines an interface that a class must implement in order to register as a PasswordProvider. The interface has one defined method, GetPassword, which accepts a Microsoft.Web.Services.Security.UsernameToken as its input parameter. The method returns the password for the given user. The idea is that in this method, you are free to implement whatever custom authentication scheme you want to use for the web service. In this example, we'll use a modified version of the Employees table of the trusty old Northwind database, to which we have added columns for username and password, to do our lookup. Since the PasswordProvider interface requires that an actual password be returned to match the password portion of the UsernameToken object, we can't use the LogonUser API directly for custom Windows Authentication without employing the clear text username/password option. Normally this would simply require that we use WSE to encrypt our username and password for transmission over the wire to the web service, but anything that has an XML representation within the <wsse:Security> header as part of either the Tokens or Elements collection can neither be signed or encrypted in WSE 1.0. MS says they will support this option in a future release. So, we would need to have a way to custom encrypt and then custom decrypt these elements in our GetPassword method -- outside of the WSE framework -- before calling LogonUser. Certainly an inconvenience; I wish Microsoft had anticipated the need for this a little better. However, it's not a concern for now, as our initial objective is to simply get to "First Base".

WSE Settings Add-In

At this point I think it would be appropriate to introduce one of the coolest arrivals that's been made part of the WSE: the Visual Studio.NET WSE Settings add-in, which is a separate download you can find at the MSDN link above. If you highlight your project in Solution Explorer and right click on it, you'll see a new context menu addition at the bottom, "WSE Settings", which brings up a neat tool that allows to to set all the important configuration and other settings for using WSE:

This enables us to easily set the Password Provider Implementation class configuration settings elements, Decryption Key Provider elements, X.509 Certificate settings, and even custom Binary Security Tokens which we desire to use. In addition, other tabs enable us to configure customized input and output filters for the WSE pipeline, routing configuration, and enable Diagnostics, which will write our input and output SOAP messages for each request/ response to files for examination. It doesn't do everything we need, but it's certainly a good start to promote ease-of-use for the WSE!

The PasswordProvider security element is a child of the parent <configuration> element in web.config, and tells WSE what class you are using to implement the PasswordProvider interface:

<microsoft.web.services>
<security>
<!--                         NAMESPACE . CLASSNAME , ASSEMBLYNAME -->
<passwordProvider type="WSESecurity.WSEPasswordProvider, WSESecurity" />
</security>
</microsoft.web.services>

So as not to lose our train of thought, let's also take a look at the code I've implemented for it in this example:

namespace WSESecurity
{
public class WSEPasswordProvider : IPasswordProvider
{
public string GetPassword(UsernameToken token)
{
try
{
SqlConnection cn = new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["SqlConn"].ToString());
cn.Open();
SqlCommand cmd = new SqlCommand("SELECT Username, password from Employees where username ='" + token.Username + "'",cn);
SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
dr.Read();
return dr["password"].ToString();
}
catch(Exception ex)
{
throw new Exception (ex.Message);
}
}
}
}

Obviously, we can get a lot more sophisticated, but the above code represents correct working code to fully implement the IPasswordProvider interface for the authentication of a user via username, password combination. Note also that there is very little we actually have to do in terms of writing code, all the plumbing is handled by WSE "under the hood".

Writing a WebMethod that employs WS-Security

Now that we've got all our security plumbing taken care of, our final portion of the web service is simply to implement whatever method(s) we want to have in our web service. Here I've implemented a method that simply runs the CustOrderHist stored procedure in the Northwind database, accepting a string UserID as the sole parameter, and returns a DataSet. If the client calling this webservice can be successfully authenticated based on the message level UsernameToken information it sends, then they will get back their DataSet. If not, they'll get back an exception telling them they didn't get authenticated. The beauty of the WSE is that with only a little bit of effort on your part, all this stuff gets done for you under the hood, so you can spend most of your development time building a product instead of building a security mechanism!

[WebMethod]
public DataSet CustOrderHist(string CustId)
{
// Only accept SOAP formatted requests
SoapContext requestContext = HttpSoapContext.RequestContext;
if(requestContext==null)
{
throw new ApplicationException("Non-SOAP request!");
}
// check all the tokens in Tokens Collection for a UsernameToken
bool valid=false;
try
{
foreach(SecurityToken tkn in requestContext.Security.Tokens)
{
if(tkn is UsernameToken)
valid=true;
}
}
catch(Exception ex)
{
throw new Exception( ex.Message + ": " + ex.InnerException.Message);
}

if (valid==false)
throw new ApplicationException("Invalid or Missing Security Token.");
SqlConnection cn;
SqlDataAdapter da;
DataSet ds;
cn = new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["SqlConn"].ToString());
cn.Open();
da = new SqlDataAdapter("custorderHist '" +CustId + "'", cn);
ds = new DataSet();
da.Fill(ds, "CustOrderHist");
return ds;
}

With the above WebMethod, and an appropriate connection string in the appSettings "SqlConn" element, we have completed a full working implementation of username / password authentication with WSE on the server side! Your WebMethod will need to have references to the Microsoft.Web.Services and Microsoft.Web.Services.Security namespaces. Now, let's complete the picture by building an ASP.NET web - based client that is capable of sending the required SOAP headers to be authenticated and successfully call our method.

Building the WSE ASP.NET Client

The WSE provides the Microsoft.Web.Services.WebServicesClientProtocol class, which derives from the System.Web.Services.Protocols.SoapHttpClientProtocol class. When you use WSDL.EXE to generate a proxy, or Add Web Reference in Visual Studio.NET, you'll need to make sure the proxy class derives from Microsoft.Web.Services.WebServicesClientProtocol and not the default System.Web.Services.Protocols.SoapHttpClientProtocol class. This derived class in WSE provides us with the new RequestSoapContext and ResponseSoapContext properties that are used to access the WS-Security conformant SOAP headers that our application and client send and receive. For C# projects, if you have used the Add Web Reference dialog, you can click the Show All Files button in Solution Explorer:


Show All Files Button

Clicking this button will expose the underlying code file, Reference.cs, under the Web References node in Solution Explorer and allow you to edit the file.

In order to create the correct UsernameToken to accompany (at the message level) our proxy method call to the web service, I use code like the following:

private void Button1_Click(object sender, System.EventArgs e)
{
localhost.SecurityServiceWse wse=new localhost.SecurityServiceWse();
UsernameToken tkn = new UsernameToken(txtUsername.Text,txtPassword.Text,PasswordOption.SendHashed);
wse.RequestSoapContext.Security.Tokens.Add (tkn);
try
{
DataSet ds=wse.CustOrderHist(txtCustID.Text);
DataGrid1.DataSource=ds;
DataGrid1.DataBind();
}
catch(Exception ex)
{
DataGrid1.Visible=false;
lblMessages.Text=ex.Message;
}
}

All we are doing here is taking the values from two TextBox input fields on the client WebForm, txtUsername and txtPassword, and combining them with the PasswordOption.SendHashed enum to create a valid UserNameToken as shown above. When the call is made to the webservice, the WSE SOAP extension validates the general format of the request, and then checks the password hash against the retrieved password from our PasswordProvider method. If it matches, we let the call go through and the client gets back its Dataset which is then bound to the Page - level Datagrid.

And Voila! We've now created a complete, working implementation of SHA1 digest hashed username / password authentication against a database with WSE! The complete working solution may be downloaded at the link below. It includes a short SQL script that will add the required username and password table columns to the Northwind Employees table, and populate the table with a new record containing the username "user1" and the password "pass1".

In the next article in this series, we'll be covering X.509 Certificate authentication and Digital Signatures of SOAP Messages.

Download the code that accompanies this article


Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.