Managing shared cookies in WCF

06/02/2009

Managing state across the HTTP protocol has always been one of the major challenges faced by developers when building applications on the web. Of course web services are no exception.

One way to overcome the stateless nature of HTTP without putting to much load on the web server, is to offload some of the information that has to be saved in the context of a particular conversation over to the client. The HTTP specification provides a native mechanism to do just that, by allowing web servers to bundle small pieces of textual data in a dedicated header of the response messages sent to the clients. These recognize the special payload, extract it, and store it in a local cache on disk to have it ready to be sent with every subsequent request. These small texts are technically known as “cookies”.

Cookies are opaque in ASMX

Cookies go back a long time in the history of HTTP, and have served the Internet (fairly) well so far. Sure they brought some serious security issues with them, but for the most part they have been a conventient way for developers of web sites/web  applications to save temporary pieces of information off the server and have it transparently sent back by the client with each request.
This guarantee comes from the factAsmxWs that every web browser on Earth has had the notion of cookies since web browser have had built-in support for cookies for the last 15 years or so.    

However, when it comes to web services, this assumption is no longer valid, since the client isn’t necessarily a web browser and doesn’t have to know how to handle cookies.

In the ASMX programming model, this problem has a quite simple solution. The client objects used to invoke operations on a web service can optionally reference an instance of a “cookie container”, were all cookies passed back by the web server are automatically stored and sent with each request.

using System.Net;

public class Program
{
  private static void Main(string[] args)
  {
    // Creates a new instance of a client proxy for an ASMX Web service
    MyWebServiceClient client = new MyWebServiceClient();

    // Creates the cookie container and assigns it to the proxy
    CookieContainer cookieJar = new CookieContainer();
    client.CookieContainer = cookieJar;

    // From now on cookies returned by any of the web service operations
    // are automatically handled by the proxy
    client.DoSomething();
  }
}

The advantage with this approach is that it is fairly opaque to the developer, which can inspect the contents of the cookie container at any time. As a bonus, it allows the same cookie container to easily be shared between multiple clients, enabling the scenarios when same cookie is required by multiple web services.

But they are transparent in WCF

In the WCF world, things are a little bit different. WCF, being a transport-agnostic technology, doesn’t allow the concept of a cookie to be directly reflected in the high level API, since it is specific to the HTTP protocol. This translate in practice in the web service client objects not having any CookieContainer property to set and retrieve.

However this isn’t necessarily a problem, since Microsoft did put a the possibility to enable automatic “behind the scenes” cookie management for HTTP clients. This of course is implemented at the WCF binding level, and can be switched on with a configuration setting:

<system.ServiceModel>
    <bindings>
        <basicHttpBinding allowCookies="true">
    </bindings>
    <client>
        <endpoint address="http://localhost/myservice"
                  binding="basicHttpBinding"
                  contract="IMyService" />
    </client>
</system.ServiceModel>

When this option is enabled the client will make sure all cookies received from a given web service are stored and properly sent on each subsequent request in a transparent fashion. But there is a catch: the cookie is only handled in the conversation with one web service. What if you need to send the same cookies to different web services?

Well, you’ll have to explicitly set the EnableCookies setting to false  (kind of counter-intuitive I know, but required nonetheless) and start managing the cookies yourself. Luckily, there are a couple of solutions.

Ad-hoc cookie management

If you wish to manually retrieve, store and send a the same given set of cookies from two different web service client objects in WCF, you could do this ad-hoc this way:

using System.ServiceModel;
using System.ServiceModel.Channels;

public class Program
{
    private static void Main(object[] args)
    {
        string sharedCookie;

        MyWebServiceClient client = new MyWebServiceClient();

        using (new OperationContextScope(client.InnerChannel))
        {
            client.DoSomething();

            // Extract the cookie embedded in the received web service response
            // and stores it locally
            HttpResponseMessageProperty response = (HttpResponseMessageProperty)
            OperationContext.Current.IncomingMessageProperties[
                HttpResponseMessageProperty.Name];
            sharedCookie = response.Headers["Set-Cookie"];
        }

        MyOtherWebServiceClient otherClient = new MyOtherWebServiceClient();

        using (new OperationContextScope(otherClient.InnerChannel))
        {
            // Embeds the extracted cookie in the next web service request
            // Note that we manually have to create the request object since
            // since it doesn't exist yet at this stage 
            HttpRequestMessageProperty request = new HttpRequestMessageProperty();
            request.Headers["Cookie"] = sharedCookie;
            OperationContext.Current.OutgoingMessageProperties[
                HttpRequestMessageProperty.Name] = request;

            otherClient.DoSomethingElse();
        }
    }
}
 
Here we are interacting directly with the HTTP messages exchanged with the web services, reading and writing the cookies as a string in appropriate headers. In order to accomplish the task we need to use the “transport-agnostic” WCF API, which indeed makes the code more verbose compared with the ASMX example.

Centralized cookie management

In situations were cookies must be managed in the same way for all web services invoked from a client applications, your best bet is to opt for a centralized solution by applying a very useful feature in WCF: message inspectors.

Message inspectors provide a hook in the WCF messaging pipeline offering the chance to look at and possibly modify all incoming or outgoing messages that transit on the server-side as well as on the client-side. The inspectors that are registered with the WCF runtime receive the messages before they are passed on to the application or sent to the wire, depending on whether it is an incoming or outgoing message.

WcfMessageInspectors

This way, it is possible to catch all HTTP responses coming from the web server, extract any cookies contained within the messages, and manually inject them in all subsequent HTTP requests on their way out. Here is a simplified view of the solution:

using System.ServiceModel;
using System.ServiceModel.Channels;

public class CookieManagerMessageInspector : IClientMessageInspector
{
    private string sharedCookie;

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        HttpResponseMessageProperty httpResponse =
            reply.Properties[HttpResponseMessageProperty.Name]
            as HttpResponseMessageProperty;

        if (httpResponse != null)
        {
            string cookie = httpResponse.Headers[HttpResponseHeader.SetCookie];

            if (!string.IsNullOrEmpty(cookie))
            {
                this.sharedCookie = cookie;
            }
        }
    }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        HttpRequestMessageProperty httpRequest;

        // The HTTP request object is made available in the outgoing message only
        // when the Visual Studio Debugger is attacched to the running process
        if (!request.Properties.ContainsKey(HttpRequestMessageProperty.Name))
        {
            request.Properties.Add(HttpRequestMessageProperty.Name,
                new HttpRequestMessageProperty());
        }

        httpRequest = (HttpRequestMessageProperty)
            request.Properties[HttpRequestMessageProperty.Name];
        httpRequest.Headers.Add(HttpRequestHeader.Cookie, this.sharedCookie);

        return null;
    }
}

Message inspectors are enabled through the WCF extensibility mechanism called behaviors for single web service operations, entire web service contracts, or even specific endpoint URLs, depending on the scope the will operate in.

Here you can download a sample application showing how to implement a client-side message inspector to share the same cookies across multiple web services.

Download Download WCF Cookie Manager

/Enrico

Advertisements

22 Responses to “Managing shared cookies in WCF”

  1. Vladimir Says:

    Thanks a lot! That’s exactly what I have been desperately looking for!
    Thank you for sharing your knowledge, you saved my time.

    Regards,
    Vladimir

  2. ubersloth Says:

    Great article!

  3. Alok Says:

    Excellent article. Looked number of places for this information.
    Thanks.


  4. […] it’s just not good. If you want to read specifically about managing cookies using inspectors this is the post that got me […]

  5. Gary Sandher Says:

    In windows phone 7 there is no support for IClientMessageInspector.

    Is there easy way to extend CookieManagerMessageInspector to support cookies management in window phone 7?

  6. steven Says:

    Thank you, this solved my problem. Surprisingly, I couldn’t find anything on the web that directly addressed this topic except this page.


  7. fantastic article. nicely formatted, great flow, and clean concise code samples.

  8. Monsignor Says:

    Great article! Unlike other ones, it contains all the information needed in one place in a well structured manner.

  9. Gary Says:

    Hi

    We have an XBAP client application that communicates via WCF services to an IIS server within the internal network. We want to internet face the application through a reverse proxy (Netware Access Manager). These are the steps.

    1) User logs into the reverse proxy server with valid username/password credentials.
    2) A session cookie in IE is created for the reverse proxy.
    3) They download the XBAP application and run it.
    4) The WCF client service calls are blocked by the Reverse proxy as they do not have a valid session cookie to pass through the portal.

    My question… is it possible to use the Internet Explorer reverse proxy session cookie cached on the client and add it to the http response header for the WCF calls so they can pass through the reverse proxy?

    Would that work?


    • Hi Gary,

      Unfortunately, the short answer is no, the XBAP application won’t be able to retrieve the proxy session cookie created by the browser.

      The reason is that XBAP applications are only able to access session cookies that originate from a requests made by the application itself. Session cookies created by the browser are out of scope. This is due to the fact that browser hosted WPF applications run outside the browser in a separate process (PresentationHost.exe), which of course can’t peek into the browser’s memory space.

      However, an XBAP application can access persistent cookies stored in the browser’s cache using the Application.GetCookie method. A possible workaround then could be to persist the proxy session cookies in cache and access them from the XBAP app using Application.GetCookie(new Uri("http://proxyserver")).

  10. Gary Says:

    Hi Enrico
    Thanks a lot…You information was most helpful to us. The reverse proxy was stripping out the session cookie before reaching the XBAP deployment/services web server as well so we had some fun there.
    The solution was to capture the reverse proxy authentication cookie on the client in javascript, pass it to the server in querystring values (forms could easily be used), then on the server recreate the cookie as a persistent cookie (with a short lifespan) from the querystring values, this new cookie is then sent to the client (auth cookies were only stripped out of requests but not responses), it is then stored on the client in the cookie cache on disk, we redirect from the client to the XBap, PresentationHost is started and it successfully reads the authentication cookie from disk. This means that the file download requests from PresentationHost for the application Manifest and subsequent download requests for the Application files all contain the authentication cookie and are therefore allowed through the reverse proxy and are successful. We then remove the persistent cookie from within the XBAP application and re-create it as a session cookie so it’s removed when the browser is closed. We then include the new session cookie for WCF communication.
    Gary

  11. Bill WInder Says:

    Very helpful, thankyou


  12. […] Managing shared cookies in WCF Share this:ShareTwitterLinkedInDiggFacebookPrintRedditStumbleUponEmailLike this:LikeBe the first to like this. […]


  13. So, I needed to reuse an auth cookie between multiple requests. I just added this

    http://stackoverflow.com/a/6157613/576446

    and now I can pass my cookie string into the inspector for each request. Thanks!

  14. tsemer Says:

    Haven’t tested just yet but must say it is written in a fluent and highly understandable manner. Very helpful, thanks!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s