Practice Safe Web Services

Use the Web Services Development Kit to integrate username and password security credentials into SOAP messages.

XtremeData

LANGUAGES: C#

TECHNOLOGIES: XML | WS-Security | SOAP | WSDK

 

Practice Safe Web Services

Use the Web Services Development Kit to integrate username and password security credentials into SOAP messages.

 

By Dan Wahlin

 

Given the variety of authentication technologies available, authenticating Web service users or applications can be a confusing task. As a developer, you can choose to authenticate callers of a Web service using HTTP Basic or Digest, client certificates, SOAP headers, or one of many other custom solutions. Although different Web service-authentication technologies have individual pros and cons, there has not been a standard way to handle authentication - until now.

 

Working together, Microsoft, IBM, and VeriSign have created a new Web service security specification, WS-Security, which simplifies Web service authentication. It also provides many other needed security essentials, such as digital signatures and SOAP message encryption. WS-Security is part of Microsoft's Global XML Web Services Architecture (GXA) framework initiative that defines a base set of protocols and facilities for building secure and reliable XML Web services.

 

According to the authors of WS-Security, its goal is to "enable applications to construct secure SOAP message exchanges" via the utilization of different XML signature and encryption technologies. You can read the complete WS-Security specification at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnglobspec/html/ws-security.asp.

 

To ease the integration of WS-Security features into .NET applications, Microsoft has released a Web Services Development Kit (WSDK) that contains different managed classes. These classes provide WS-Security, WS-Routing, and WS-Attachments/DIME functionality (DIME is used for adding attachments to SOAP messages). In this article, I'll introduce you to some of these WS-Security-specific classes in the WSDK that you can use to authenticate Web service consumers by adding username and password security credentials within SOAP messages. (Although not discussed here, the WSDK also supports authentication through X.509 certificates.) Note that when this article was written, WSDK was in beta; code and concepts discussed herein might change before WSDK goes live.

 

Configure Your Apps to Use the WSDK

After installing Microsoft's WSDK (you can download it from http://msdn.microsoft.com/webservices/building/wsdk/default.asp), you need to add several entries to the web.config file associated with your Web service application. These entries are necessary to associate a WSDK HTTP Module with your Web service and to provide additional configuration and debugging details. The following code shows the HTTP Module configuration code you need to add within the <system.web> element of the web.config file:

 

<httpModules>

    <add name="WSDK"

        type="Microsoft.WSDK.HttpModule, Microsoft.WSDK" />

</httpModules>

 

After adding the HTTP module configuration code, you should add the code shown in Figure 1 so you can debug and trace incoming and outgoing SOAP messages more easily. You need to place this code within the web.config's <configuration> root tag.

 

<microsoft.wsdk>

    <diagnostics>

        <trace enabled="true"

            input="ClientRequest.xml"

            output="ServiceResponse.xml" />

        <faultDetail enabled="true" />

    </diagnostics>

</microsoft.wsdk>

Figure 1. The diagnostics element can contain trace and faultDetails configuration elements that allow WSDK developers to access more detailed error information during the development phase. Once your Web service authentication code is running correctly, you should disable tracing and fault details by setting their respective enabled attribute values to false.

 

The trace element in Figure 1 is quite useful when you need to debug incoming and outgoing SOAP messages. By setting the trace element's enabled attribute to true, you enable logging of SOAP messages automatically. The location of the log files is defined in the trace element's input and output attributes. The faultDetail element allows more specific error messages to be returned from the Web service in cases where an error occurs during the processing of a SOAP message by WSDK classes. When the enabled attribute of the faultDetail element is set to false, you'll receive a general error message such as "Server unavailable, please try later" as errors do occur.

 

Create the Web Service

Now that you've added WSDK configuration entries to the web.config file, you can begin working with the different classes used to integrate WS-Security into your Web services. Fortunately, you don't have to add much code to your service's Web Methods to use authentication because the WSDK does most of the work. You need to add code to verify that a username is passed in the SOAP request message; this prevents those instances where a Web service client tries to use the service directly without passing security credentials. With exception to that particular code check, the WSDK handles checking the password against the username, as you soon will see. This allows you to separate the functionality offered by the Web service from the application's security implementation rules.

 

The GetCustomers Web Method shown in Figure 2 queries a database and returns a DataSet containing customer details. The Web Method calls a private method, EnsureUserName, which checks that a username is passed in the SOAP message.

 

[WebMethod]

public DataSet GetCustomers() {

    //Ensure a username was passed.

    //The checking of the user's password will be done

    //in a separate class.

    if (EnsureUserName(

        HttpSoapContext.RequestContext.Security)) {

        DataSet ds = new DataSet("NorthwindCustomers");

        string sql = "SELECT * FROM CUSTOMERS";

        string connStr =

            ConfigurationSettings.AppSettings[

                "XMLforASPDSN"];

        SqlConnection dataConn =

            new SqlConnection(connStr);

        SqlDataAdapter da =

            new SqlDataAdapter(sql,dataConn);

        da.Fill(ds,"Customers");

        return ds;

    } else {

        throw new SoapFault("No user name specified!",

             new XmlQualifiedName(

              "error","http://www.xmlforasp.net"));

    }      

}

Figure 2. GetCustomers ensures that a username is passed in the SOAP request message by calling a method named EnsureUserName. If a username is passed (and it matches up with a password, as you'll see in the next section), the database will be queried and a DataSet returned from the Web Method; if no username is passed, a SOAP fault will be thrown.

 

Looking at the code in Figure 2, notice the appearance of a class you're likely unfamiliar with: HttpSoapContext. This class is part of the WSDK and is located within the Microsoft.WSDK namespace. You need to import this namespace, as well as the Microsoft.WSDK.Security namespace, into your Web service class to use HttpSoapContext and other related classes.

 

HttpSoapContext lets you access specific parts of a given request or response SOAP message. For this example, the security section of the incoming SOAP request message is accessed so we can ensure a username is passed from the Web service client. This is accomplished in the EnsureUserName method by enumerating through the different security token objects (created after deserializing the SOAP message) and looking for a token of type UsernameToken. (You'll learn more about the UsernameToken class later in the article when I discuss the Web service proxy.) The complete code for the EnsureUserName method is shown in Figure 3.

 

private bool EnsureUserName(Security secToken) {

    bool valid = false;

    if (secToken.Tokens.Count > 0 ) {

        foreach (SecurityToken tok in secToken.Tokens ) {

            // Determines if the current security token

            // is a UsernameToken.

            UsernameToken token = tok as UsernameToken;

            if (token != null ) {

                 valid = (token.Username == null ||

                token.Username == String.Empty)?false:true;

            }

        }

    }

    return valid;

}

Figure 3. The EnsureUserName method accesses the security tokens in the incoming SOAP request message and finds the UsernameToken token. If this token contains a value, the method returns true. Otherwise the Web service client did not pass a username and false is returned.

 

Implement the IPasswordProvider Interface

Aside from writing the Web Method and ensuring a username is passed in the request SOAP message, you also need to write a class that implements the WSDK's IPasswordProvider interface. This interface defines a single method named GetPassword that accepts the username as a parameter and returns the password associated with that user. Behind the scenes, WSDK classes call this method automatically when a username is found in a SOAP message. Figure 4 shows how to create a simple class that implements the IPasswordProvider interface.

 

using System;

using Microsoft.WSDK.Security;

namespace Codebank.WebServices.WSDK_SecurityCredentials {

  public class WSSecurityPassword: IPasswordProvider {

    public WSSecurityPassword()  {}

      public string GetPassword(string userName) {

        //We would normally query the db

        //(or other store) here

        //and get the password associated with the userName

        return "password";

      }

   }

}

Figure 4. The IPasswordProvider interface must be implemented by a .NET class in order for WSDK username authentication to work properly. The WSSecurityPassword class shown here implements this interface and its GetPassword method. Notice that the class imports the Microsoft.WSDK.Security namespace.

 

Although this particular example hardcodes a password for the sake of simplicity, you normally would query a user database table (or other store) based on the username and return the associated password for that user. WSDK classes automatically compare the returned password to the password supplied by the caller of the Web service.

 

It is not enough to create this class and compile it; you also need to let the WSDK SOAP processing pipeline know about it. To do this, add the following configuration section within the <microsoft.wsdk> beginning and ending tags described earlier (see Figure 1). This section identifies the IPasswordProvider namespace and class as well as the assembly name (xmlforasp in this case):

 

<!--Identify the namespace, class, and assembly

    of the password class -->

<security>

    <passwordProvider        

        type="Codebank.WebServices.WSDK_SecurityCredentials.

              WSSecurityPassword,xmlforasp" />

</security>

 

Create and Use the Web Service Proxy

Now that you have created the Web service and the necessary WSDK password provider class, you can build a Web service proxy to make sending and receiving SOAP messages to and from the service easier. To build the proxy, you can use the generator utility found in Visual Studio .NET and ASP.NET Web Matrix, or you can use the WSDL.exe command-line utility that comes with the .NET Framework.

 

After creating the proxy, you need to change the name of the base class your proxy class extends:

 

public class ProxyClassName:

    System.Web.Services.Protocols.SoapHttpClientProtocol {}

 

You should change the code to:

 

public class ProxyClassName:

    Microsoft.WSDK.WSDKClientProtocol {}

 

This change is necessary to allow the client proxy access to specific WSDK features such as passing user authentication credentials. After making the change, compile the proxy and import its namespace into an ASP.NET Web form. The Web form also should import the Microsoft.WSDK and Microsoft.WSDK.Security namespaces.

 

The Web form in the sample application available with this article's downloadable code provides two textboxes so an end user can enter a username and password. Clicking on the form's submit button captures the click event on the server side and invokes the Web service proxy object. Rather than immediately calling the Web service's GetCustomers method through the proxy, you can add some initial code so the username and password are passed in the SOAP request message via the proxy. Figure 5 contains this code. (Note that several comments are included in the listing to explain what occurs at each step.)

 

private void btnSubmit_Click(object sender,

 System.EventArgs e) {

  try {

    //Create usernameToken class and

    //cause password to be hashed

    UsernameToken token =

     new UsernameToken(txtUserName.Text,txtPassword.Text,

     PasswordOption.SendHashed);

 

     //Create proxy

     SecurityCredentialsProxy proxy =

     new SecurityCredentialsProxy();

 

     //Get Request SOAP context and

     //add user token to its tokens

     SoapContext context = proxy.RequestSoapContext;

     context.Security.Tokens.Add(token);

 

     //Add Time to Live to prevent tampering.

     //This message will expire in 1 minute

     context.Timestamp.Ttl = 60000;

 

     //Call WebService and bind data to DataGrid control

     DataSet ds = proxy.GetCustomers();

     dg.DataSource = ds;

     dg.DataBind();

     this.lblOutput.Text = String.Empty;

     this.pnlDataGrid.Visible = true;

  } catch (Exception exp) {

     this.lblOutput.Text = exp.Message;

     this.pnlDataGrid.Visible = false;

  }

}

Figure 5. Before calling the Web service's methods through the proxy object, a UsernameToken class is created so the user's name and password are passed as SOAP headers. This allows the user to be authenticated by the WSDK SOAP processing pipeline. Because passwords are sensitive, the password is hashed before sending it.

 

The code starts by passing the user's username and password to the UsernameToken class's constructor. The password is hashed to keep prying eyes from viewing it as it is sent across the wire. The "hashing" is accomplished by passing the PasswordOption.SendHashed enumeration value. You also may choose to send the password as clear text in cases where Secure Sockets Layer (SSL) is used, or not send the password at all.

 

After creating the UsernameToken object, the request SOAP context is accessed through the HttpSoapContext object and the token is passed to its Security.Tokens collection. This sends the username and password automatically in a SOAP header to the Web service. The code then sets the expiration time for the SOAP message through the TimeStamp.Ttl property. The value passed represents milliseconds, but doing so ensures the message has a limited lifetime, which makes the messaging process that much more secure.

 

After a user logs in to the Web form, a SOAP message is sent to the Web service. This contains several different security sections, including user authentication token information as well as SOAP message expiration details. WSDK classes handle creating the different SOAP elements and attributes automatically.

 

Upon deserializing the SOAP request message, the WSDK processing pipeline invokes the WSSecurityPassword class shown in Figure 4, which returns the correct password associated with the username. This password is compared to the one entered by the user. Note that the password returned from the GetPassword method is hashed automatically so a correct comparison can be made. If the passwords match, the target Web Method will be executed successfully; otherwise a fault will be returned to the calling client.

 

Ultimately, the WSDK provides exciting new WS-Security functionality you can use to secure Web services in several ways. This article has demonstrated just one of the toolkit's features you can use to authenticate users based on username and password. Although the WSDK is currently in "technology preview" mode, learning it now will prepare you for the future of Web services.

 

The files referenced in this article are available for download.

 

Dan Wahlin received Microsoft's Most Valuable Professional award in the ASP.NET category. He is president of Wahlin Consulting and founded the XML for ASP.NET Developers Web site (http://www.XMLforASP.NET), which focuses on using XML and Web Services in Microsoft's .NET platform. He also is a corporate trainer and speaker, and he teaches XML and ASP.NET training courses around the United States. Dan co-authored the books Professional Windows DNA (Wrox) and ASP.NET: Tips, Tutorials & Code (Sams), and he wrote XML for ASP.NET Developers (Sams). E-mail Dan at [email protected].

 

Tell us what you think! Please send any comments about this article to [email protected]. Please include the article title and author.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish