Social Icons

twitter google plus linkedin rss feed

Pages

6.9.12

Authenticating an ASMX Web Service in SharePoint from a WinRT app

Where’s the CookieContainer?

Yes mate, I’ve had that question too.

We are creating a new application for Windows 8 and we need it to connect to our web services. Connecting to the anonymous methods was easy, but when it comes to authenticating the user the situation changes.

I was pretty confident, in this context confident is an euphemism for ignorant and naive,  I could use the same approach I used when I was creating my apps for WP7 I have used it a thousand times and WP7 is more or less the same as WinRT so who would have thought it won’t work?
Well WinRT is not the same as WP7.

After creating the SPAuthBridge class I tried to add the authentication cookie to my SoapClient as usual but as you probably now if you are reading this post this line doesn’t work:

MySoapClient.CookieContainer = SharePointAuth.cookieJar;

In WinRT the SoapClient object doesn’t have a CookieContainer object so we have to find another way of adding the cookie in the calls.

The SPAuthBridge is OK. I have just added a new method to it to get the authentication cookie as string from the cookie container.
public string GetAuthenticationCookie()
{
    return string.Format("{0}={1}", "FedAuth",
            cookieJar.GetCookies(new System.Uri("https://www.mySite.com"))["FedAuth"].Value);
}

Then all you have to do is to follow the steps in the previous post to authenticate and after you have successfully authenticated to SharePoint you have to change the way you add the cookie to your SoapClient.

I have found two ways:

The quick one

Every time you need to call the web service you have to add the cookie to the header and then call the web service in the same OperationContextScope. It looks like this:
using (new OperationContextScope(MyWS.InnerChannel))
{
    HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();

    httpRequestProperty.Headers[System.Net.HttpRequestHeader.Cookie] = SharePointAuth.GetAuthenticationCookie();

    OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;

    UserInfo = await MyWS.GetInfoForUserAsync();
}

That’s easy but it’s not very readable and if you have to call a lot of web services you’ll probably get bored of this approach.

The elegant one

Instead of manually adding the cookie every time we can also create a behaviour that does it automatically.

In order to do that you need to create a CookieBehavior class. I copied 99% of mine from here and it looks like this:
class CookieBehaviour: IEndpointBehavior
{
    private string cookie;

    public CookieBehaviour(string cookie)
    {
        this.cookie = cookie;
    }

    public void AddBindingParameters(ServiceEndpoint serviceEndpoint,
        BindingParameterCollection bindingParameters) { }

    public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint, ClientRuntime behavior)
    {
        behavior.ClientMessageInspectors.Add(new CookieMessageInspector(cookie));
    }

    public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint,
        EndpointDispatcher endpointDispatcher) { }

    public void Validate(ServiceEndpoint serviceEndpoint) { }
}

public class CookieMessageInspector : IClientMessageInspector
{
    private string cookie;

    public CookieMessageInspector(string cookie)
    {
        this.cookie = cookie;
    }

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

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        HttpRequestMessageProperty httpRequestMessage;
        object httpRequestMessageObject;
        if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name
            , out httpRequestMessageObject))
        {
            httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
        }
        else
            httpRequestMessage = new HttpRequestMessageProperty();

        httpRequestMessage.Headers[System.Net.HttpRequestHeader.Cookie] = cookie;
        request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);

        return null;
    }
}

Once you have this class the only thing you have to do is to add this behaviour to your web service on the initialization and your cookie will be added automatically every time.
public MyWSSoapClient InitializeMyWS()
{
    BasicHttpBinding Bind = new BasicHttpBinding();

    //////////////////////Transport///////////////////////
    Bind.Security.Mode = BasicHttpSecurityMode.Transport;
    //////////////////////////////////////////////////////

    /////////////////////MessageSize//////////////////////
    Bind.MaxReceivedMessageSize = Int32.MaxValue;
    //////////////////////////////////////////////////////

    EndpointAddress oAddress = new EndpointAddress(SiteUrl + "/_vti_bin/MyWS.asmx");
    MyWSSoapClient MyWS = new MyWSSoapClient(Bind, oAddress);

    CookieBehaviour AuthCookieBehaviour = new CookieBehaviour(SharePointAuth.GetAuthenticationCookie());
    MyWS.Endpoint.EndpointBehaviors.Add(AuthCookieBehaviour);

    return MyWS;
}

After you have initialized the web service like this you can call your methods without worrying about the authentication any more:
private async Task<ObservableCollection<Info>> GetInfo()
{
    GetInfoForUserResponse Information = await MyWS.GetInfoForUserAsync();

    return Information.Body.GetInfoForUserResult;
}
The transport is necessary in my case because I am calling a web service hosted in an HTTPS web application by default is set to None and I was getting an ArgumentException like this:

The provided URI scheme 'https' is invalid; expected 'http'.

And the MaxReceivedMessageSize could also be a wee bit excessive. Tune it to suit your needs but be aware that if you set it too small you’ll get a nice CommunicationException saying that:

The maximum message size quota for incoming messages (65536) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element.

Have fun with the tablet if you have one, I am still waiting for mine.