Screen Orientations in Windows Phone 7 Updated articles for "Mango"

Know your cookies

Published on Thursday, August 4, 2011 11:16:20 AM UTC in Programming

Yesterday I stumbled upon a bewildering detail when I worked with Forms Authentication and Windows Phone 7 that cost me quite some time to fix and work around, so I decided to blog about it to save others from the same trouble.

The setup in particular was that I talked to a WCF RESTful service from Windows Phone 7, using the HttpWebRequest class. Part of this was doing the authentication, which used Forms Authentication. If you are familiar with the concept, then you know that this authentication uses cookies to store an authentication ticket (unless configured not to use cookies). The cookie is sent to the server with each request to identify an authenticated user. As it turns out, working with this concept on Windows Phone 7 is not really straight forward, partly due to restrictions of Silverlight, partly because of problems that are caused by the platform itself.

The first problem

When you use a analyzing tool like Fiddler, you can take a look at how the authentication mechanism works on the network traffic level. In an authentication request, the client sends the user name and password. The service tries to validate these credentials, and if that was successful, the built-in features of Forms Authentication allow you to set the corresponding cookie. This in turn results in a header like this returned to the client:

Set-Cookie: .ASPXAUTH=76[...]D7; expires=Tue, 09-Aug-2011 08:57:53 GMT; path=/; HttpOnly

The first problem you're facing when you use HttpWebRequest on Windows Phone is that you don't seem to receive the cookie. Although the header is there when you look at the traffic, on the client both the content of the cookie container is empty and the raw http header also is not accessible.

HttpWebRequest request = (HttpWebRequest)WebRequest.CreateHttp(someUrl);
request.CookieContainer = new CookieContainer;

// do the request, write your data etc., then get the response
using (var response = (HttpWebResponse)request.EndGetResponse(asyncResult))
{
  if (response.StatusCode != HttpStatusCode.OK)
  {
    // is zero!
    var cookieCount = request.CookieContainer.Count;

    // is null!
    var rawHeader = response.Headers["Set-Cookie"];
  }
}

What is happening here? Well, when you take a look at the raw header above, you can see that the last part there says "HttpOnly". This is a security flag that prevents client-side scripting from accessing this cookie. The details are explained here. Client-side scripting also includes Silverlight, and in particular also Silverlight on the phone, which I wasn't aware of. This mechanism is sealed pretty tightly; you cannot even see that data when you look at your cookie container details in the debugger. It's simply as if it is not there, period.

The very important thing though is, that even if you can't see it, it still is there. The runtime manages these cookies for you, and your reference to them is in fact the cookie container.

A first solution

So what you can do to solve this is retain a reference to the cookie container and reuse it when you do the next requests, like this:

HttpWebRequest request = (HttpWebRequest)WebRequest.CreateHttp(someUrl);
request.CookieContainer = _theCookieContainer;
request.BeginGetResponse(...);

This in fact will send the previously acquired authentication token with the request and if you take a look at the server side you can confirm that the user in fact will be authenticated. Good.

The next problem arises when you have to deal with the application life cycle on the phone, in particular with tombstoning. You might think about doing something like this when the user navigates away from your page (or application):

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    if (e.NavigationMode != NavigationMode.Back)
    {
        if (State.ContainsKey("Cookies"))
        {
            State.Remove("Cookies");
        }

        State.Add("Cookies", _cookieContainer);
    }

    base.OnNavigatedFrom(e);
}

And then, in the OnNavigatedTo override, restore the information like:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    if (State.ContainsKey("Cookies"))
    {
        _cookieContainer = (CookieContainer)State["Cookies"];
    }
}

This apparently works, the cookie container in fact is restored. The problem however is, that the hidden information about the authentication token is not preserved by this, which means that you cannot be authenticated anymore and have to repeat the authentication process with your service. This is also true for situations where you try to persist the authentication state e.g. in isolated storage to preserve it for multiple application sessions. That is pretty inconvenient and results in additional network calls that need to be performed every time.

Note: in Mango, this is slightly better because your application will be tombstoned less often. The default dormant state, if handled correctly, will retain the state of the cookie container correctly and hence no additional authentication round-trip is required in that case. However, you're still unable to persist the authentication state permanently and restore it later.

A second solution

Another way to solve this is to turn off the HttpOnly flag for the authentication cookie. *Wait a minute!! Didn't you just tell me this is a security feature? *Yes I did. To fully understand how this feature helps increasing security, I recommend reading this article, for example. Basically the scenario is that a maliciously injected client-side script (so-called XSS vulnerability) sends the cookie to a third party server. Once an attacker has obtained access to the cookie, they can interact with the original site by impersonating you (e.g. using your cookie for authentication with that site). It's obvious to realize this is something you want to avoid in any case.

On Windows Phone, making use of a vulnerability like this would mean that the attacker must be able to manipulate the http requests you are making from your client application. More than that, they would need to manipulate the requests of other users on different devices (why would they want to steal their own authentication cookies). Admittedly, even though much harder than for normal web applications, it doesn't seem impossible for all scenarios. So let's say an attacker managed to compromise your app in a way that the http requests intended to go out to your service now go to the hacker's server. Would using the HttpOnly flag in that case help? No. Because the first solution described above uses the cookie container to send out the authentication token to the malicious site. The same would happen there.

So on the phone, all this mechanism does is prevent you (the legit user) to access that data; it doesn't add security in the sense of preventing third-party access. I surely don't recommend turning HttpOnly off in general – don't! But in a dedicated service for your Windows Phone app, I see no problem with this (feel free to discuss!).

One way to do this is to alter the authentication cookie and set it manually, instead of letting Forms Authentication do it:

// don't use the generated cookie
//FormsAuthentication.SetAuthCookie(userName, false);

// instead, change the HttpOnly flag, then set it manually
var cookie = FormsAuthentication.GetAuthCookie(userName, false);
cookie.HttpOnly = false;
HttpContext.Current.Response.Cookies.Add(cookie);

Another way of doing this is hooking into the corresponding event and alter the cookie there. This is the way you need to go if you're using the default authentication service implementation of ASP.NET Application Services. The process is described here, for example. Now if you inspect the response that arrives on the client, you can see that the cookie is in fact available in the cookies collection (or the cookie container you provided):

AuthCookie

Although persisting that cookie object directly and restoring it doesn't work that well neither (it will likely throw an error when you add it to the cookie container after deserializing), you now have full access to the cookie data and can extract the required data to persist it easily, like:

// create dictionary
var values = new Dictionary<string, string>();
values.Add("Name", _cookie.Name);
values.Add("Value", _cookie.Value);

// add data for serialization
State.Add("Cookie", values);

And then restore and use it like:

// retrieve dictionary
var values = (Dictionary<string, string>)State["Cookie"];

// re-create cookie
var cookie = new Cookie(values["Name"],
                        values["Value"]);
_cookie = cookie;

// ... then use cookie
HttpWebRequest request = (HttpWebRequest)WebRequest.CreateHttp(someUrl);
request.CookieContainer = new CookieContainer();
request.CookieContainer.Add(someUrl, _cookie);

Conclusion

I hope this explanation helps you handling the forms authentication token/cookie in your own Windows Phone application, should you ever need to, and to avoid the problems I initially ran into. You can of course build upon that and create an even better and improved solution, for example by respecting the expiration date.

Tags: Authentication · HttpWebRequest · WCF · Windows Phone 7