December 20, 2014
Hot Topics:

Build Secure Web Services With SOAP Headers and Extensions

  • April 17, 2003
  • By Jeff Prosise
  • Send Email »
  • More Articles »

The Quotes3 Web Service

Quotes2.asmx is a step in the right direction because it transmits authentication data in every request. Using SOAP headers for transport means the information is passed out-of-band and that it isn't tied to any particular protocol, such as HTTP. But Quotes2.asmx still leaves room for improvement. Currently, every Web method has to check user names and passwords. The next logical step is to get the authorization code out of the Web methods and into a module that examines incoming requests as they arrive and rejects those lacking valid credentials.

A SOAP extension is the perfect tool for the job. Physically, a SOAP extension is a class derived from System.Web.Services.Protocols.SoapExtension. Logically, it's an object that sits in the HTTP pipeline and enjoys access to the SOAP messages entering and leaving. Quotes3.asmx (Figure 5) demonstrates how to use a SOAP extension to extract user names and passwords from SOAP headers. Observe that GetQuote no longer performs any checking of its own. Instead, it's attributed with [AuthExtension], which associates it with the SOAP extension class named AuthExtension. [AuthExtension] is an instance of AuthExtensionAttribute, which is a custom attribute class derived from the Framework's SoapExtensionAttribute class. In AuthExtensionAttribute, the following override ensures that AuthExtension will be called for any method attributed with [AuthExtension]:

public override Type ExtensionType
{
    get { return typeof (AuthExtension); }
}

The SOAP extension class AuthExtension derives from SoapExtension. Its heart is its ProcessMessage method, which is called four times every time GetQuotes is invoked: once before the message is deserialized by the Framework, once after it's deserialized but before QuoteService.GetQuote is called, again after QuoteService.GetQuote has executed but before the response is serialized into a SOAP message, and a final time after the response is serialized. AuthExtension checks the message after it is deserialized by the .NET Framework. If the message contains no AuthHeader or if it contains an AuthHeader with invalid credentials, AuthExtension rejects it by throwing a SoapException. Significantly, GetQuote no longer has to do any authentication itself, because it never sees a message that hasn't been authenticated already.

Figure 5: Quotes3.asmx

<%@ WebService Language="C#" Class="QuoteService" %>

using System;
using System.Web.Services;
using System.Web.Services.Protocols;

[WebService (
    Name="Quote Service",
    Description="Provides instant stock quotes to registered
                 users"
)]
public class QuoteService
{
    public AuthHeader Credentials;

    [AuthExtension]
    [SoapHeader ("Credentials", Required=true)]
    [WebMethod (Description="Returns the current stock price")]
    public decimal GetQuote (string symbol)
    {
        if (symbol.ToLower () == "msft")
            return 55.0m;
        else if (symbol.ToLower () == "intc")
            return 32.0m;
        else
            throw new SoapException ("Unrecognized symbol",
                SoapException.ClientFaultCode);
    }
}

public class AuthHeader : SoapHeader
{
    public string UserName;
    public string Password;
}

[AttributeUsage (AttributeTargets.Method)]
public class AuthExtensionAttribute : SoapExtensionAttribute
{
    int _priority = 1;

    public override int Priority
    {
        get { return _priority; }
        set { _priority = value; }
    }

    public override Type ExtensionType
    {
        get { return typeof (AuthExtension); }
    }
}

public class AuthExtension : SoapExtension
{
    public override void ProcessMessage (SoapMessage message)
    {
        if (message.Stage == SoapMessageStage.AfterDeserialize) {
            //Check for an AuthHeader containing valid
            //credentials
            foreach (SoapHeader header in message.Headers) {
                if (header is AuthHeader) {
                    AuthHeader credentials = (AuthHeader) header;
                    if (credentials.UserName.ToLower () ==
                        "jeff" &&
                        credentials.Password.ToLower () ==
                        "imbatman")
                        return; // Allow call to execute
                    break;
                }
            }

            // Fail the call if we get to here. Either the header
            // isn't there or it contains invalid credentials.
            throw new SoapException ("Unauthorized",
                SoapException.ClientFaultCode);
        }
    }

    public override Object GetInitializer (Type type)
    {
        return GetType ();
    }

    public override Object GetInitializer (LogicalMethodInfo info,
        SoapExtensionAttribute attribute)
    {
        return null;
    }

    public override void Initialize (Object initializer)
    {
    }
}

For an added touch, you could move AuthHeader, AuthExtensionAttribute, and AuthExtension to a separate source code file, compile them into a DLL, and drop the DLL in the Web service's bin directory. Then, the ASMX file would reduce to this:

<%@ WebService Language="C#" Class="QuoteService" %>

using System;
using System.Web.Services;
using System.Web.Services.Protocols;

[WebService (...)]
public class QuoteService
{
    public AuthHeader Credentials;

    [AuthExtension]
    [SoapHeader ("Credentials", Required=true)]
    [WebMethod (Description="Returns the current stock price")]
    public decimal GetQuote (string symbol)
    {
      ...
    }
}

If you add more methods to the Web service, simply decorate them with [AuthExtension] and [SoapHeader] attributes and they, too, will be authenticated by the SOAP extension.

Summary

Quotes3.asmx demonstrates how to combine SOAP headers and SOAP extensions to authenticate callers to Web services and to do so without explicitly authenticating in the Web methods themselves. Do be aware that authenticating callers by encoding plaintext user names and passwords in SOAP headers is not secure unless you encrypt the message traffic. One way to encrypt SOAP messages is to write a SOAP extension that unencrypts requests and encrypts responses. Another way to do it is to use SSL—that is, to publish the Web service at an HTTPS address. How you do it isn't important; what's important is to avoid passing unencrypted credentials over unsecured connections.

About the Author...

Jeff Prosise is a cofounder of Wintellect, a consulting, debugging, and education firm that specializes in .NET. His latest book, Programming Microsoft .NET, was published last year by Microsoft Press. Jeff is also a columnist for MSDN Magazine and asp.netPRO Magazine.





Page 2 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel