Vantage Point: Bob German's Weblog

Notes from BlueMetal Architects, where Bob is SharePoint Principal Architect. Here you will find postings on all things SharePoint, especially developer related topics.

Anonymous Client Access to SharePoint 2010

Anonymous Client Access to SharePoint 2010

Rate This
  • Comments 2

I recently embarked on a minor quest to get the SharePoint 2010 Client Object Model working on an anonymous SharePoint site, with mixed results. Along the way, I learned a lot about how the Client Object Model works, and eventually found a work-around for most of what I wanted to accomplish.

Anonymous Access in SharePoint

Although SharePoint supports a number of different kinds of authentication, it’s also possible to waive authentication entirely and allow users to connect anonymously. This needs to be turned on explicitly at both the web application and site collection levels. I won’t repeat the instructions for doing this as they’re already posted in so many places. Here’s a good reference directly from Microsoft. It also explains how to control what anonymous users can access at a site and list level.

Once you’ve enabled anonymous access, users can visit your site without authenticating. Notice that in the screen shot, there’s a “Sign In” link in the upper right corner where the username is usually shown. When a user clicks this link, he or she is authenticated using whatever authentication is configured for the web application.

Authentication in the Client Object Model

The Client OM comes in three flavors: one for Javascript, one for Silverlight, and one for general use in .NET assemblies. All three use the same WCF service (Client.svc) on the SharePoint server side, but each has its own client side proxy that works in the browser, Silverlight, and .NET runtime environments respectively.

The client proxies each handle authentication a little differently. Since Javascript and Silverlight ride on the browser’s networking stack, they don’t have explicit control over the authentication method, but implicitly communicate over the already established browser connection to SharePoint. The managed code proxy allows you to control the authentication in code. For example,

clientContext.AuthenticationMode =
     ClientAuthenticationMode.FormsAuthentication;

FormsAuthenticationLoginInfo formsAuthInfo =
     new FormsAuthenticationLoginInfo("MyUser", "MyPassword");

clientContext.FormsAuthenticationLoginInfo = formsAuthInfo;

This snippet is from a detailed article on authentication in the client OM, which explains how you can connect anonymously, using Forms authentication, or “default” authentication, which allows for transparent Active Directory authentication.

In my case, I wanted to use the Client OM in a Silverlight web part, so there was nothing special to do: the site connection was anonymous, and so was the Silverlight connection. In fact, it even worked! Except for one problem …

Roadblock!

At first you may be lulled into thinking you’ve got it working; the Client OM is able to connect and do many things under anonymous authentication.

And then, it hits you.

The code below is pretty simple: it shows some items from a SharePoint list. But it doesn't work for anonymous users!  That's right, you can’t retrieve items from a SharePoint list as an anonymous user. Now reading items from a SharePoint list is an extremely useful thing to do, so it’s kind of a show stopper when you can’t do it. The anonymous user can view the list in SharePoint with no problem, pull its RSS feed with abandon, but the client OM won’t do it.

Here’s the code; all it does is show a message box displaying the titles of the items in a list.

ClientContext ctx = ClientContext.Current;
Web web = ctx.Web;
 
List descList = web.Lists.GetByTitle("MyList");
CamlQuery q = CamlQuery.CreateAllItemsQuery();
ListItemCollection liCollection = descList.GetItems(q);
ctx.Load(liCollection);
 
ctx.ExecuteQueryAsync(
    // Success
    (s1, args1) => {
        Dispatcher.BeginInvoke(() =>
        {
            string listItems = "";
            foreach (ListItem li in liCollection)
            {
                listItems += li["Title"].ToString() + "\n";
            }
            MessageBox.Show(listItems);
        });
    },
    // Failure
    (s2, args2) => {
        Dispatcher.BeginInvoke(() =>
        {
            MessageBox.Show(args2.ErrorTypeName);
        });
    });
}

Here’s the same code in Javascript, by the way, for those who prefer to program the browser directly:

function execClientOM() {

    context = SP.ClientContext.get_current();

    var list = context.get_web().get_lists().getByTitle(“myList”);

    var camlQuery = SP.CamlQuery.createAllItemsQuery();
    this.listItems = list.getItems(camlQuery);
 
    context.load(listItems);
 
    context.executeQueryAsync(ReadListItemSucceeded,
                              ReadListItemFailed);
}
 
function ReadListItemSucceeded(sender, args) {
    var itemsString = '';
    var enumerator = listItems.getEnumerator();
 
    while (enumerator.moveNext()) {
        var listItem = enumerator.get_current();
        itemsString += listItem.get_item('Title') + '\n';
    }
 
    alert(itemsString);
}
 
function ReadListItemFailed(sender, args) {
    alert('Request failed. ' + args.get_message() + '\n' +
          args.get_stackTrace());
} 

The Javascript version is based on a The Client OM and JQuery, which is a book excerpt that’s been posted on MSDN. I highly recommend this article, because it really explains how the Client OM works. The client proxy classes that your code interacts with are actually generated from the Microsoft.SharePoint server-side classes by specifying a special attribute. Actually I have to recommend the rest of the book as well, Professional SharePoint 2010 Branding and User Interface Design, which covers everything you ever wanted to know about customizing the SharePoint UI and is a great resource. The chapter, by the way, was written by Paul Stubbs, with whom I just coauthored another book in which he reveals still more Client OM magic.

Anyway, you can try it: in both Silverlight and Javascript, the code works for an authenticated user, but an anonymous user gets a Microsoft.SharePoint.Client.ApiBlockedException. It will fail for the managed code version as well, because indeed the API is blocked on the server.

A really clever guy from Oslo actually cracked the code on this in his blog article, Anonymously accessing list items through the SharePoint Client Object Model. He found the ClientCallableSettings property of the SPWebApplication object; this property controls which API calls are prohibited in the client OM.  Apparently the mapping from client to server OM follows all the way through, and the server can put the kibosh on calls it doesn’t want to allow for anonymous users. Administrators can even dial in the calls they want to prohibit or permit; the article conveniently how to turn off blocking the GetItems() call using PowerShell:

$webapp = Get-SPWebApplication “http://somesite/
$webapp.ClientCallableSettings.AnonymousRestrictedTypes.Remove
          ([microsoft.sharepoint.splist], "GetItems")
$webapp.Update()

Nice; thanks Einar! Running the PowerShell commands does indeed fix the problem, and anonymous users can suddenly access list items. It leaves me wondering, however, if there’s some security reason they blocked this API. In case you’re wondering, here is the list of API’s that appear to be blocked by examining ClientCallableSettings:

  • SPSite.GetChanges()
  • SPWeb.GetChanges()
  • SPWeb.GetSubwebsForCurrentUser()
  • SPList.GetChanges()
  • SPList.GetItems()

Disallowing GetChanges() makes sense to me; this is for reading the change log and is typically used in search indexing, so it doesn’t make sense to be able to read it without authenticating. I’m not sure about the rest; the Norwegian blogger points out that list security is still enforced even if you override the setting. He also notes that, ironically, there’s no prohibition on writing content.

In my case, unfortunately, this was a non-starter because we’re using SharePoint in the cloud and there’s no way the hosting provider is going to run our PowerShell command. It’s back to square one on reading a list.

A Sandboxed Alternative

The hosting provider does, however, allow sandboxed solutions, which means we can run code on the server side! The trick to making this work, then, is to read the list on the server and pass it on the web page to the client. This wouldn’t work in every case; most notably, it assumes that when rendering the web page we already know what content the client will need. In this case I threw caution to the wind and downloaded the whole list.

Here’s the server code. Notice that it stuffs the list content into a generic list of Description objects, which are defined elsewhere in my code. This list is then serialized and placed in a hidden form field for Silverlight to consume. The details on how to do this are in Chapter 7 of my book, SharePoint 2010 Development with Silverlight;  you can download a similar example from the MSDN Code Gallery site.

HiddenField hidden = new HiddenField();
SPWeb web = SPContext.Current.Web;
SPList list = web.Lists["MyList"];
 
Descriptions descriptions = new Descriptions();
descriptions.items = new List<Description>();
 
foreach (SPListItem li in list.Items)
{
    Description desc = new Description
    (
        li["Title"].ToString(),
        li["Description"].ToString(),
        li["DescriptionType"].ToString(),
    );
    descriptions.items.Add(desc);
}
hidden.Value = descriptions.Serialize();
this.Controls.Add(hidden);

The hidden field’s client ID is then passed to the Silverlight application; it could alternately be passed to Javascript. Here’s the Silverlight code, which has already read the hidden field’s client ID from InitParams:

HtmlDocument doc = HtmlPage.Document;
HtmlElement element = doc.GetElementById(this.controlID);

if (element != null && element.GetAttribute("value") != null)
{
    string jsonString = element.GetAttribute("value").ToString();
    if (jsonString != "")
    {
        Descriptions descriptions = Descriptions.Load(jsonString);
 
        string result = "";
        foreach (Description desc in descriptions.items)
        {
            result += desc.Title + "\n";
        } 
        MessageBox.Show(result);
    }
}

This works fine, and has the advantage of avoiding an extra round-trip to the server … assuming, that is, that you know in advance what data is needed. It’s also nice that it’s type-safe on both the client and server sides.

Writing Confidential Data

Finally, the application was reading the SharePoint content it needed for anonymous users! The last piece of the puzzle is that anonymous users need to write information to the site, information that includes personally identifiable information and thus must be kept confidential.

As Einar Otto Stangvik’s blog article (referenced earlier) points out, while reading a list isn’t possible, writing it is no problem. The issue is that there’s no way to give write-only access to a SharePoint list; if anonymous users can write to a list, they can also view it in the SharePoint UI or as an RSS feed. This presented a dilemma for the confidential information.

The first idea for a solution was to have a workflow move the item from one list, where anonymous users have read/write access, to another list, where anonymous users have no rights. SharePoint Designer has a User Impersonation Step feature that should allow this by running part of the workflow as a more privileged user who has access to both lists. Alas, however, by default anonymous users can’t start workflows! Again, a clever blogger has found a work-around, but it requires a farm administrator to run the necessary command, which rules it out for me.

This part of the story is still unfinished. Perhaps the confidential data belongs somewhere outside of SharePoint, like in SQL Azure …

Summary

While it is possible to use the Client OM without authenticating, there are some major limitations you need to get around. I hope this posting has helped you to find the right solution to your situation, and saved you a little time along the way.

Thanks!

Blog - Comment List MSDN TechNet
  • Loading...
Leave a Comment
  • Please add 7 and 6 and type the answer here:
  • Post