RIA Services Authentication Out-Of-Browser

RIA Services does not support Out-Of-Browser (OOB) Forms authentication using the Client networking stack out of the box. The Client stack is often recommend for OOB scenarios as it provides a greater level of control over requests. The reason authentication does not work on the Client networking stack is that it handles cookies differently than the Browser networking stack. However, with a little coaxing RIA Authentication can be made to work in this scenario as well.

The first step is to switch to using the Client networking stack. The solution that follows cannot be applied when you’re using the Browser networking stack. It’s a good idea to take a look at how to enable the Client stack in your application. Often, it will only require the addition of these two lines in the App constructor.

 WebRequest.RegisterPrefix("https://", WebRequestCreator.ClientHttp);
WebRequest.RegisterPrefix("https://", WebRequestCreator.ClientHttp);

The simple solution to this problem is to force each DomainContext to use a shared CookieContainer. When the Authentication DomainContext gets authenticated, it will store the cookies there and all subsequent operations will pass them back to the server. To make it simple to add cookie sharing to an application, I put together a static utility class.

 /// <summary>
/// Utility for enabling Authentication using the ClientHttp networking stack.
/// </summary>
public static class ClientHttpAuthenticationUtility
{
    private static readonly SharedCookieHttpBehavior SharedCookieBehavior =
        new SharedCookieHttpBehavior();

    public static void ShareCookieContainer(DomainContext context)
    {
        PropertyInfo channelFactoryProperty =
            context.DomainClient.GetType().GetProperty("ChannelFactory");
        if (channelFactoryProperty == null)
        {
            throw new InvalidOperationException(
                "There is no 'ChannelFactory' property on the DomainClient.");
        }
        ChannelFactory factory = (ChannelFactory)
            channelFactoryProperty.GetValue(context.DomainClient, null);
        ((CustomBinding)factory.Endpoint.Binding).Elements.Insert(
            0, new HttpCookieContainerBindingElement());
        factory.Endpoint.Behaviors.Add(
            ClientHttpAuthenticationUtility.SharedCookieBehavior);
    }

    private class SharedCookieHttpBehavior : WebHttpBehavior
    {
        private readonly SharedCookieMessageInspector _inspector =
            new SharedCookieMessageInspector();

        public override void ApplyClientBehavior(
            ServiceEndpoint endpoint, 
            ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(this._inspector);
        }
    }

    private class SharedCookieMessageInspector : IClientMessageInspector
    {
        private readonly CookieContainer _cookieContainer =
            new CookieContainer();

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            // do nothing
        }

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            // make sure the channel uses the shared cookie container
            channel.GetProperty<IHttpCookieContainerManager>().CookieContainer =
                this._cookieContainer;
            return null;
        }
    }
}

Now to get this working, we need to apply this solution to each DomainContext. The easiest way to do this is to add a call to the partial OnCreated method generated for each Silverlight context.

 namespace Web
{
    public partial class AuthenticationContext
    {
        partial void OnCreated()
        {
            ClientHttpAuthenticationUtility.ShareCookieContainer(this);
        }
    }

    namespace Services
    {
        public partial class MyDomainContext
        {
            partial void OnCreated()
            {
                ClientHttpAuthenticationUtility.ShareCookieContainer(this);
            }
        }
    }
}

It’s important to make sure the namespaces match when implementing partial methods. I included this snippet in my client project (within SampleNamespace) and added methods for SampleNamespace.Web.AuthenticationContext and SampleNamespace.Web.Services.MyDomainContext.