For-Fee Web Services

One of the most commonly asked questions regarding Web services has to do with economics rather than programming conundrums. The question can be summarized as follows. I鈥檓 thinking about writing a particular Web service. For it to be commercially viable, I have to charge a fee for using it. If I charge for it, I have to know who鈥檚 calling it. How do I know who鈥檚 calling my Web service? In other words, how do I identify the originator of a Web method call?

The question is actually one of security. Identifying callers means authenticating callers. There are several ways to authenticate Web service callers. Here are three possibilities:

The third of these three options is arguably the most compelling because it transmits authentication data out-of-band and enables developers to leverage the high-level support for SOAP headers built into the .NET Framework. To demonstrate, here鈥檚 a Web service whose Add method can be called successfully only if the SOAP request contains a header named AuthHeader that in turn contains a UserName element equal to 鈥渏effpro鈥?and a Password element equal to 鈥渋mbatman鈥?

<%@聽WebService聽Language="C#" Class="SafeService" %>

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

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

class聽SafeService
{
聽聽聽聽public聽AuthHeader聽header;

聽聽聽聽[WebMethod]
聽聽聽聽[SoapHeader聽("header",聽Required="true")]
聽聽聽聽public聽int聽Add聽(int聽a,聽int聽b)
聽聽聽聽{
聽聽聽聽聽聽聽聽if聽(header.UserName聽== "jeffpro" &&
聽聽聽聽聽聽聽聽聽聽聽聽header.Password聽== "imbatman")
聽聽聽聽聽聽聽聽聽聽聽聽return聽a聽+聽b;
聽聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽聽聽聽throw聽new聽HttpException聽(401, "Not聽authorized");
聽聽聽聽}
}

AuthHeader is a custom type derived from System.Web.Services.Protocols.SoapHeader that represents a SOAP header. The SoapHeader attribute affixed to the Add method tells ASP.NET to reject calls (that is, throw SoapHeaderExceptions) to Add that lack AuthHeaders and to map incoming AuthHeaders to the SafeService field named header. The Add method extracts the user name and password from the SOAP header and throws an exception if the credentials are invalid.

How does a client attach AuthHeaders to outgoing calls? Here鈥檚 an excerpt from a .NET Framework client that calls SafeService鈥檚 Add method with the proper authorization header:

SafeService聽calc聽=聽new聽SafeService聽();
AuthHeader聽header聽=聽new聽AuthHeader聽();
header.UserName聽= "jeffpro";
header.Password聽= "imbatman";
calc.AuthHeaderValue聽=聽header;
int聽sum聽=聽calc.Add聽(2,聽2);

The AuthHeader data type is defined in the service鈥檚 WSDL contract. When Wsdl.exe generated the Web service proxy (SafeService), it also generated an AuthHeader class definition and added an AuthHeader field named AuthHeaderValue to SafeService. Initializing that field with an AuthHeader object transmits the data contained therein in a SOAP header. Here, in fact, is what the outgoing SOAP envelope might look like, with the SOAP header highlighted in boldface text:

<?xml聽version="1.0" encoding="utf-8"?>
<soap:Envelope聽xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
聽聽xmlns:xsd=http://www.w3.org/2001/XMLSchema
聽聽xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
聽聽<soap:Header>
聽聽聽聽<AuthHeader聽xmlns="http://tempuri.org/">
聽聽聽聽聽聽<UserName>jeffpro</UserName>
聽聽聽聽聽聽<Password>imbatman</Password>
聽聽聽聽</AuthHeader>
聽聽</soap:Header>
聽聽<soap:Body>
聽聽聽聽<Add聽xmlns="http://tempuri.org/">
聽聽聽聽聽聽<a>2</a>
聽聽聽聽聽聽<b>2</b>
聽聽聽聽</Add>
聽聽</soap:Body>
</soap:Envelope>

As you can see, the .NET Framework goes to great lengths to insulate you from the nuts and bolts of SOAP and XML. It even provides high-level abstractions for SOAP headers.

A variation on this technique is to include an Application_Authenticate- Request handler in a Global.asax file and examine SOAP headers there. The advantage to this approach is that you don鈥檛 have to authenticate the caller in each and every Web method. Application_AuthenticateRequest centralizes the authentication code and allows you to prevent the Web method from even being called if the caller lacks the proper credentials.

Yet another way to identify callers is to configure IIS to require authenticated access to the directory that hosts the Web service and enable Windows authentication in ASP.NET via a Web.config file. Then derive your Web service class from System.Web.Services.WebService and use the inherited User property to obtain callers鈥?user names. This form of authentication, unlike methods that perform custom authentication using SOAP headers, is appropriate only if callers have accounts in the Web server鈥檚 domain. In other words, it鈥檚 a fine solution for Web services that serve clients on the company鈥檚 intranet but not for ones that serve the population at large.