Microsoft MVP Logo

This post is part of a series on Office365/SharePoint Online, Windows Azure and Authentication. The other posts in this series are as follows:

Now let's see how we can address the authentication fixes for each of the different ways you can access SharePoint remotely. In this post I'll cover each of the specific tools (REST or OData / CSOM / Web Services / WebClient) and how to address each of the tricks. Each one has it's pros & cons, hence why I had to use all four tools in my demo in my breakout session Out of the Sandbox and into the cloud: Build your next SharePoint app on Azure at the Microsoft SharePoint Conference 2011 (see that link for where you can download the sample).

Any code samples I show in this post were taken from my session Out of the Sandbox and into the cloud: Build your next SharePoint app on Azure at the Microsoft SharePoint Conference 2011... you can get the demo code from the Critical Path Training site's Members section... look for the AC's SharePoint Conference 2011 Sessions download in the Presentations section.

For all the samples below, I created a private property in my class called MsftOnlineClaimsHelper that creates a local instance of the MSO helper and automatically authenticates.

private MsOnlineClaimsHelper _claimsHelper;
/// 
/// Microsoft Online claims helper used to authenticate to SharePoint Online.
/// 
private MsOnlineClaimsHelper MsftOnlineClaimsHelper {
  get {
    if (_claimsHelper == null) {
      _claimsHelper = new MsOnlineClaimsHelper(
                RoleEnvironment.GetConfigurationSettingValue("SharePointUsername"),
                RoleEnvironment.GetConfigurationSettingValue("SharePointPassword"),
                RoleEnvironment.GetConfigurationSettingValue("SharePointRootSiteUrl"));
    }
    return _claimsHelper;
  }
}

CSOM Client Context & CBA Challenges

One of the most common ways to work with SharePoint 2010 from off the SharePoint server is using the CSOM. Authentication with the CSOM is pretty straight forward using the ClientContext object. The trick comes into play with claims based authentication (CBA).

When you want to switch to FBA it's a simple property switch on the ClientContext, but as I previously stated there is no such way to do this for CBA. What you need to do is rewire the ClientContext so that every request it makes to a site collection includes a SAML token to authenticate the request. You do this by trapping the ExecutingWebRequest event of the ClientContext and injecting the cookie container generated by the MSO helper into all requests:

private ClientContext _clientContext;
/// 
/// CSOM client context.
/// 
private ClientContext CsomClientContext {
  get {
    if (_clientContext == null) {
      _clientContext = new ClientContext(
        RoleEnvironment.GetConfigurationSettingValue("SharePointSiteUrl")
      );

      // wire up claim helper to include SAML tokens (cookies) in all requests
      _clientContext.ExecutingWebRequest += 
            (webRequestSender, args) => {
                args.WebRequestExecutor.WebRequest.CookieContainer 
                    = MsftOnlineClaimsHelper.CookieContainer;
            };
    }
    return _clientContext;
  }
}

Now, almost all requests the ClientContext make will include the SAML token! I say "almost" because there is a bit of an issue with the ClientContext. There is a method called File.OpenBinaryDirect() that you can use to download a file from SharePoint. For some reason this method doesn't raise the same ExecutingWebRequest event so your token isn't handled! Ouch... oversight in the API me thinks... regardless, you can get around this using a stock Web Client...

Web Request & CBA Challenges

The way you can address the lack of passing along the SAML token to SPO when you try to open and download a file using the File.OpenBinaryDirect() method is to simply create a simple Web request that will download the file. However this process also needs a little bit of work to pass along the SAML token. What I did was create a custom version of the WebClient class that did this for you as follows:

namespace AndrewConnell.ACsCichlids.StoreFront {
  public class ClaimsFriendlyWebClient : WebClient {
    private CookieContainer _cookieContainer;
    public CookieContainer CookieContainer {
      get { return _cookieContainer; }
      set { _cookieContainer = value; }
    }

    protected override WebRequest GetWebRequest(Uri address) {
      var request = base.GetWebRequest(address);
      if (request is HttpWebRequest)
        ((HttpWebRequest)request).CookieContainer = _cookieContainer;

      return request;
    }
  }
}

This method is handy when you want to download a file from a site collection. To use it you simply pass in the MSO helper's cookies and they will be included on all requests:

using (var webclient = new ClaimsFriendlyWebClient() 
    { CookieContainer = MsftOnlineClaimsHelper.CookieContainer }
  ) {
      // download file into a memory stream
      var fileStream = ((ClaimsFriendlyWebClient)webclient).OpenRead(cichlidPicture.OriginalUri);

      // create & save blob
      var blob = AzureStorageContainer.GetBlobReference(cichlidPicture.ImportedFilename);
      blob.UploadFromStream(fileStream);
}

REST / OData / Web Services & CBA Challenges

My preferred way to read/write data to SharePoint lists is using the RESTful OData service ListData.svc. This service, like all the other Web services that are included with SharePoint 2010 (*.ASMX & *.SVC), don't understand claims by default. When you want to authenticate for a Windows or FBA site you have to create a network credential object and set it as a property on the service proxy.

However this isn't available when it comes to authenticating with CBA. Like the ClientContext, you need to rewire the calls to make sure they include the SAML token in each request. This is pretty simple as most services, like the Lists.asmx service, includes a CookieContainer property:

XmlNode results;

// call lists.asmx web service to get attachments for each item 
//    use same "cookie container" technique to authenticate
using (var client = new Lists() {
  Url = siteUrl + "/_vti_bin/Lists.asmx",
  UseDefaultCredentials = true,
  CookieContainer = MsftOnlineClaimsHelper.CookieContainer
}) {
  results = client.GetAttachmentCollection(
    RoleEnvironment.GetConfigurationSettingValue("SharePointManagementListName")
    , listItemId.ToString()
  );
}

There you have it... hopefully this series & code samples will help you authenticate into your site collections in SharePoint Online!

Comments powered by Disqus