span.sup { vertical-align:text-top; }

Cutting Edge

Building A Secure AJAX Service Layer

Dino Esposito

Code download available at:CuttingEdge2008_09a.exe(469 KB)

Contents

The Service Layer
Implementing an AJAX Service Layer
Built-in Protection for Service Calls
Implementing Security in the AJAX Service Layer
Retrieving User Information from the Service
Wrapping It Up

A major advantage of AJAX and Silverlight™ applications is that they can transparently and continuously interact with a back-end service. The problem is that they run over HTTP, which wasn't designed with security in mind. Yet it's important that you protect these back-end services from unauthorized access. So what do you do?

The architecture of a typical AJAX or Silverlight application is shown in Figure 1 . It's essentially a two-tiered architecture with the front and back ends neatly separated by the Internet. As a result of this architecture, there is opportunity for unauthorized access between the presentation layer and the back end.

Figure 1 A Typical Architecture for a Rich Web Client App

The back end exposes its API through a collection of public URLs. These URLs refer to hosted services that are technically part of the application, yet are exposed as public services. Any high-security barrier you could place here (such as a firewall) would stop authorized as well as unauthorized calls. It would be quite difficult to completely weed out the unwanted access. Obviously, this would be much easier in an intranet or extranet scenario.

When you choose to take advantage of a Web-based distributed architecture like this, you must expect outsiders to find your services and attempt to exploit them.

The Service Layer

Modern distributed systems have an extra layer of code between the presentation and business layers—the service layer. Formalized by Martin Fowler in his book Patterns of Enterprise Application Architecture, the service layer in a distributed application represents an additional layer of code that creates a boundary between two other layers, in this case between the presentation and business layers.

The code in the presentation layer typically implements use cases. A typical use case is a sequence of actions performed by the user that result in interactions between one or more business objects, workflows, and services. The service layer allows you to abstract these smaller interactions with an intermediate API, exposed through more coarsely granular services. The presentation layer makes one call to one service in the layer. The invoked service layer method will coordinate the objects and workflows in the business layer to implement the required behavior. Figure 2 shows the benefits of the service layer.

fig02.gif

Figure 2 The Service Layer Decouples Presentation and Business Layers

In the service layer implementation, it is common to assume that callers have been previously authenticated and authorized. Also, it is common to place the service layer in an isolated part of the network, for example behind a firewall. If the client is an ASP.NET application, you call the service layer from within the page code. However, because this code executes on the Web server, the call to the service is essentially a server-to-server call and occurs in a protected and controlled environment. No new security concerns arise in this scenario.

Now imagine a service layer in an ASP.NET AJAX or Silverlight scenario. To enable AJAX or Silverlight clients to call into the service layer, the address of the service layer's endpoints must be publicly exposed. This means removing any firewalls you may have in the middle.

Only publicly exposed functionality can be invoked from a publicly facing, rich Web client. In protecting your back-end service layer, you could implement a pared-down version of the functionality in question in your ASP.NET AJAX or Silverlight clients. This might mean, for example, that your Silverlight client shows pending orders (a non-critical operation), but is not allowed to place a new order (a critical operation). In the end, though, if you want full functionality, you need to address security.

Implementing an AJAX Service Layer

An extra layer of services—what I call the AJAX service layer—can prevent some of the unauthorized access I described. This AJAX service layer would sit in front of the traditional service layer and mediate communication between the AJAX or Silverlight presentation layer and the rest of the back-end system. The URLs of services in the AJAX service layer will be public and open to anybody who can discover them. Communication between applications and this layer is easy and not blocked in any way.

The AJAX service layer I'm recommending would not implement any application logic. It operates as an AJAX-specific gateway to the back-end system, and its sole purpose is to implement security. At the same time, however, it keeps AJAX-specific security code out of business and application services.

The AJAX service layer consists of the ASMX Web services, Windows® Communication Foundation (WCF), or representational state transfer (REST) services, that you will call from the user interface. These services perform two actions. First, they verify that the user on whose behalf the action is requested has sufficient permissions to perform the action. This check is typically about membership and roles. Once all security checks pass, the service in the AJAX service layer forwards the call down to the real application service layer. Figure 3 describes the AJAX service layer and its connection to the application service layer.

fig03.gif

Figure 3 The AJAX Service Layer Isolates the Service Layer from Unauthorized Calls

Any call that passes through the AJAX service layer is considered safe and is allowed to reach the core system. The communication between the AJAX service layer and the service layer can be further secured using firewalls, IPsec, certificates and whatever else you can think of.

The AJAX service layer includes any service you would call directly from within an ASP.NET AJAX or Silverlight 2 application. It therefore consists of local, same-domain services you call via a direct URL (such as the WebClient class in Silverlight 2) or through a JavaScript proxy. If your user interface is based on a suite of third-party controls, any services you may bind to members of these controls (such as for paging or in-place editing purposes) should be secured as part of the AJAX service layer.

Keep in mind, however, that implementing security comes at a cost. So if a particular service doesn't really perform any operations that must be secured, you might want to leave it unprotected. This is an acceptable option and one that many Web sites employ. An autocompletion Web service, for example, has little to hide. Web sites that use autocompletion connect to a Web service to get a list of suggestions. This is a read-only operation that doesn't modify any data on the server or reveal any trade secrets. Leaving it unsecured presents little risk.

Of course, any time you allow an outsider to get into your system you are giving him a chance to exploit latent and unknown security holes. Getting calls from all sorts of clients is a necessity in AJAX, but recognizing levels of risk is a must. You can always place different services in different sub-domains.

Built-in Protection for Service Calls

ASP.NET Web services and WCF service endpoints automatically have the advantage of a built-in security barrier. By default, WebMethods of an ASMX Web service cannot be invoked from an HTTP GET call. In addition, a POST request works only if the content type is set to a particular string: application/json. This simple mechanism stops calls made as part of a script injection attack. Once some malicious script is injected in a page, a URL can only be invoked through a <script> tag. A <script> tag can also reach a cross-domain URL, but it must use an HTTP GET and cannot set headers or content type. At the same time, any downloaded script code that attempts to execute within the JavaScript engine of the local browser will be sandboxed and thus will not be able to reach any URL— local or remote.

By default, an ASP.NET AJAX or Silverlight client invokes a WCF service using an HTTP POST. To enable an HTTP GET (and to modify the format of the URL) you have to configure the service appropriately. The default content type in this case is also set to application/json.

In light of this, is there still something to be worried about? You bet.

Once the URL or static IP address of a target service has been discovered, it is relatively trivial for hackers to replay a call. The aforementioned barriers may prevent them from reaching the service using the browser—the browser always uses a GET—but there are other ways. What are they? Quite simply, they could sniff the browser-to-service communication. And once all details have been worked out, they could use a .NET smart client to prepare a call. Figure 4 shows a call to illustrate the point.

Figure 4 Full-Trust Code to Invoke

void Button1_Click(object sender, EventArgs e)
{
    string url = "https://contoso.com/services/service.asmx/GetCompletionList";
    WebRequest request = WebRequest.Create(url);
    request.Method = "POST";
    request.ContentType = "application/json";
    Stream stmReq = request.GetRequestStream();
    StreamWriter writer = new StreamWriter(stmReq);
    writer.Write(@"{""prefixText"":""Lon"",""count"":5}");
    writer.Close();

    WebResponse response = request.GetResponse();
    Stream stmResp = response.GetResponseStream();
    StreamReader reader = new StreamReader(stmResp);
    string text = reader.ReadToEnd();

    reader.Close();
    stmResp.Close();
}

The code uses the WebRequest and WebResponse classes in the System.Net namespace in a Windows Forms application to make a direct call. Because the application runs as full trust, no restrictions apply, and anybody who has the URL and call details can remotely invoke your service.

Implementing Security in the AJAX Service Layer

To figure out ways to protect your AJAX service layer effectively, you should look again at Figure 3 . Two groups of users may call services in the AJAX service layer: legitimate users and outsiders. Legitimate users connect through a regular Web front end, be it ASP.NET AJAX or Silverlight 2. Outsiders reach the URL using any platform they can—usually a custom full-trust application.

To allow access to legitimate users and reject malicious ones, you should identify a piece of information that only legitimate users can easily provide. As Figure 5 shows, that special piece of information is the authentication cookie.

fig05.gif

Figure 5 Legitimate Users Connect After Passing Through a Login Interface

The key is to isolate in a protected user interface element—an ASP.NET page in this case—any function that needs to invoke a sensitive service method. By requiring that any user who wants to invoke that functionality pass through a login page first, you guarantee that any subsequent requests originated by that user carry the authentication cookie issued by the application's membership system.

The login page gets credentials from the user and verifies whether the user is authorized to visit the page. If all is fine, the request is authorized and an authentication cookie is generated and attached to the response. From then on, any requests the user makes to the application, including service requests, will carry the cookie. As a result, any invoked services in the AJAX service layer should check for the authentication cookie to ensure that a legitimate user is making the request.

Any page that carries security-sensitive operations in ASP.NET AJAX or Silverlight 2 applications should be placed in a protected area of the Web site. To filter access to a specific area, add the following configuration fragment to the web.config file:

<location path="MembersOnly">
    <system.web>
        <authorization>
            <deny users="?" />
        </authorization>
    </system.web>
</location>

Here, the contents of the MembersOnly folder are restricted to authenticated users. Finally, you need to turn on authentication. In this example, I'm enabling ASP.NET forms authentication:

<authentication mode="Forms">
    <forms loginUrl="MembersOnly/login.aspx">
    </forms>
</authentication>

Admittedly, this is not rocket science.

Once a user has successfully visited the login page, a valid authentication cookie is attached to the response. This cookie will be used for any subsequent request until it expires.

It is interesting that this approach works for both ASP.NET AJAX and Silverlight 2. The Silverlight plug-in, in fact, relies on the browser's underlying infrastructure (which is only visible to plug-ins) to execute remote calls. This ensures that authentication cookies (and any other cookies) are appended to the request.

Retrieving User Information from the Service

The authentication cookie issued by the application is passed to the Web service or WCF service. But how can the service access this information? Let's tackle ASMX Web services first.

An ASMX Web service is always hosted by the ASP.NET worker process; thus it has access to the HTTP context of the ASP.NET request being processed. If you derive the Web service class from the WebService class defined in the System.Web.Services namespace, then your class will inherit the Context property and will then have access to all of its properties, including Session, Request, Response, and, especially, User. If you choose not to derive your class from WebService, you can still access the same ASP.NET intrinsic objects using the HttpContext.Current property. In any case, the ASP.NET request context is always available to an ASMX Web service.

For a WCF service, things are different. A WCF service can be hosted in IIS either side-by-side with ASP.NET or in compatibility mode. The default configuration is side-by-side with ASP.NET.

Requests for a WCF service are received by the ASP.NET run time, but it doesn't participate in the processing of these requests. Within the worker process, the WCF run time detects incoming requests and processes them through the WCF stack.

When ASP.NET and WCF work side-by-side, there are a few side effects to note. For example, you can't define an access control list to the SVC resource and, more important in this context, the HttpContext.Current property always returns null when accessed from within a WCF service. As a result, there's no way for the WCF service to grab any information about the ASP.NET logged-in user.

In general, the WCF model is designed to work regardless of the hosting environment or transport mechanism. However, security over the AJAX service layer requires a stricter collaboration between WCF and the host ASP.NET environment. This is just what the ASP.NET compatibility mode for WCF addresses.

When running in compatibility mode, a WCF service fully participates in the lifecycle of the ASP.NET request. The net effect is that the WCF service has access to the same information as an ASMX Web service. In particular, file-based authorization is supported and, more important, HttpContext.Current is correctly set.

Compatibility mode is a global setting enabled at the application level. Individual services, though, are allowed to refuse or accept the mode. To turn on compatibility mode, you need to enter the following fragment in the configuration file:

<system.serviceModel>  
  <serviceHostingEnvironment 
     aspNetCompatibilityEnabled="true" />    
</system.serviceModel>

Individual services declare their support for compatibility mode through the RequirementsMode property of the AspNetCompatibilityRequirements attribute, which is to be set on the service class, not the contract. Values for the property are Required, Allowed, and NotAllowed. The default is NotAllowed, which means that each WCF service in the AJAX service layer must have the RequirementsMode property changed to either Allowed or Required:

[AspNetCompatibilityRequirements(
    RequirementsMode = AspNetCompatibilityRequirementsMode.Required)] 
public class TimeService : ITimeService
{
   :
}

Based on this information, each critical method in a service within the AJAX service layer should incorporate a piece of code that checks the identity of the logged-in user. Here's an example:

public class TimeService : ITimeService
{
   public DateTime GetTime()
   {
      CheckIdentity();
      ...
   }
}

The CheckIdentity function is an arbitrary function that reads the identity of the user as propagated by the authentication cookie and resolved by the ASP.NET pipeline:

private void CheckIdentity()
{
  string user = HttpContext.Current.User.Identity.Name;
  if (AuthenticationHelper.VerifyUser(user))
      return;

  throw new InvalidOperationException("Invalid credentials");
}

In this case, CheckIdentity is designed to throw an exception if no user name is available or if the user name is not in the list of authorized users. You can also change the implementation to make the method return a Boolean value and let the service method decide what to do.

With this design in place, any outsider making a call to the service without passing through the login page will send an HTTP packet devoid of the authentication cookie. As a result, the User object in the HTTP context is null and CheckIdentity throws an exception. How secure is this approach? It is as secure as forms authentication itself, which is one of the pillars of the ASP.NET platform. The outsider has just one way to succeed: by stealing an authentication cookie and using it before it expires.

ASMX and WCF services take advantage of the information stored in the ASP.NET HTTP context. What about REST services in the AJAX service layer? If you implement a REST service using the WCF Web programming model in the Microsoft® .NET Framework 3.5, then you are covered by WCF compatibility mode. However, if you create a custom service by simply calling a URL and having it return plain old XML or JSON (JavaScript Object Notation) data, then the service endpoint is merely an ASP.NET endpoint and it has full access to the HTTP context.

Wrapping It Up

Services in the AJAX service layer can be secured as discussed here regardless of the client platform, be it ASP.NET AJAX or Silverlight 2. The AJAX service layer includes all public endpoints that a rich Web client needs to invoke to accomplish its tasks. These services play an essential role in the life of the application, yet they are public endpoints. As such, they can also be invoked by outsiders, creating an inherent security hazard.

To protect these services, you can force users to pass through a login page so that an ASP.NET authentication cookie is attached to the request and moved around with subsequent requests. The cookie allows you to differentiate between legitimate users and outsiders. You can't avoid public endpoints, but you need a way to recognize authorized users and keep others off your site.

Send your questions and comments for Dino to cutting@microsoft.com .

Dino Esposito is an architect at IDesign and also the author of Programming ASP.NET 3.5 Core References. Based in Italy, Dino is a frequent speaker at industry events worldwide. You can join his blog at weblogs.asp.net/despos .