Share via


SharePoint 2010: Using the Client Object Model with a Form Based Auth Site - different way

Using SharePoint's Client object Model against a SharePoint site that is not using IWA (Kerberos or NTLM) is not supported out of the box (at least not as clean as I would like).  If the site is using Forms Based Authentication and you can prompt the user for the user name and password, it can be done by using the following:

Using the Client Object Model with a Forms Based Auth Site in SharePoint 2010

ClientContext ctx = new  ClientContext(someSiteUrl); 
ctx.AuthenticationMode = ClientAuthenticationMode.FormsAuthentication; 
ctx.FormsAuthenticationLoginInfo = new  FormsAuthenticationLoginInfo(myUserName, myUserPwd); 
 
//get the web 
Web w = ctx.Web; 
 
//LOAD LISTS WITH ALL PROPERTIES 
var lists = ctx.LoadQuery(w.Lists); 
 
//execute the query 
ctx.ExecuteQuery(); 
 
foreach (List theList in lists) 
{ 
        //do something here
  
}

The above approach is OK, but means storing the user name and password for the user everytime you want to call to SharePoint with Client OM as the user logged in to your app.  Doing so is probably not the best practice and the alternative is to prompt the user before every call to SharePoint from the calling application.  Other approaches I saw popped up a WebControl like a mini-browser for the user to auth to SharePoint then they used some COM calls to get the Auth Cookies as it was set HTTPONLY and not directly accessible.  

I have used the following approach to solve this problem.  In a later article I will add the functionality to utilize this approach for Authenticating to SharePoint site using a custom STS for authentication. 

Notes

This approach was built to support ASP.NET web applications calling into SharePoint 2010 via the Client Object Model.  However, the underlying concept can be used from console apps, win forms and windows services.  This solution took a lot of fiddler snooping and debugging and seems like it should be easier to do this but this is what I was able to get working.  Once I got it working it did not seem too bad but sure seems like a Rube Goldberg approach. 

This approach assumes that the ASP.NET application and SharePoint Site are using the same user repository,  In my case I was using SQL Server Membership and Role Provider.  This ensures that logging into the ASP.NET Forms Auth Login Page can use the same user/pass to back channel log into SharePoint.  The follow up article on using STS makes this a moot point.

Approach

Outline

  1. Customize ASP.NET forms auth page to add the back channel calls to SharePoint to get a SharePoint AuthCookie.
  2. Store the SharePoint AuthCookie in Session Variable
  3. Add a custom EventHandler to ClientRuntimeContext.ExecutingWebRequest that uses the AuthCookie from session to authenticate to SharePoint Site.

Step 1

On the Login EventHandler that your forms authentication page uses authenticate to you call to SharePoint to get an auth cookie.  If using the ASP.NET Login control the LoggingIn Event is a good spot.

protected void  LoginUser_LoggingIn(object sender, LoginCancelEventArgs e)
{
    //  URL to the SharePoint Forms Login Page
    String SiteLogin="https://my.FBASharePointSite.com/_forms/default.aspx";
 
    try
    {
        String AuthToken = getAuthToken(SiteLogin, LoginUser.UserName, LoginUser.Password);
 
    }
    catch (Exception ex)
    {
 
    }
}

This method below basically mimics what a browser would do and does a login to the SharePoint forms auth page and captures the FedAuth cookie generated on a successful login.
Note:  The variables posted to your SharePoint forms auth page may be different especially if you use a custom login page.  Fiddler can be a huge help on determining what the values are and what a valid auth post looks like.

private String getAuthToken(String SiteLogin, String UserName, String Password)
{
 
    // have a cookie container ready to receive the forms auth cookie
    CookieContainer cookies = new  CookieContainer();
 
    // Initial hit to SharPoint site
    // load EventValidation and ViewState
    //  Allow AutoRedirects
    // set required HTTP HEADERS
    System.Net.HttpWebRequest SPHttpWebRequest = (HttpWebRequest)WebRequest.Create(SiteLogin);
    SPHttpWebRequest.AllowAutoRedirect = true;
    SPHttpWebRequest.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1";
    SPHttpWebRequest.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
    SPHttpWebRequest.Headers.Add("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
    SPHttpWebRequest.CookieContainer = cookies;
    //  Retrieve HTTP Response from the site
    HttpWebResponse SPHttpWebResponse = (HttpWebResponse)SPHttpWebRequest.GetResponse();
 
    StreamReader responseReader = new  StreamReader( SPHttpWebResponse.GetResponseStream() );
    string responseData = responseReader.ReadToEnd();
    responseReader.Close();
 
    // Scrape ViewState and EventValidation from Response
    string viewState = ExtractViewState(responseData);
    string eventValidation = ExtractEventValidation(responseData);
     
 
    // Authentication Request to get forms auth Token through a form post.
    HttpWebRequest SPAuthhttpWebRequest = HttpWebRequest.Create(SPHttpWebResponse.ResponseUri) as  HttpWebRequest;
    SPAuthhttpWebRequest.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0.1) Gecko/20100101 Firefox/8.0.1";
    SPAuthhttpWebRequest.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
    SPAuthhttpWebRequest.Headers.Add("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
    SPAuthhttpWebRequest.ContentType = "application/x-www-form-urlencoded";
    //Specifing the Method
    SPAuthhttpWebRequest.Method = "POST";
    SPAuthhttpWebRequest.AllowAutoRedirect = false;
    SPAuthhttpWebRequest.CookieContainer = cookies;
    //Data to Post to the Page, it is key value pairs; separated by "&"
    // Submit button value for this form is "Sign+In" 
    string postData =
    String.Format(
       "__LASTFOCUS=&__EVENTTARGET=&__EVENTARGUMENT=&__VIEWSTATE={0}&__EVENTVALIDATION={1}&ctl00$PlaceHolderMain$signInControl$UserName={2}&ctl00$PlaceHolderMain$signInControl$password={3}&ctl00$PlaceHolderMain$signInControl$login={4}",
       viewState, eventValidation, UserName, Password, "Sign+In");
    //Setting the content type, it is required, otherwise it will not work.
 
    //Getting the request stream and writing the post data
    using (StreamWriter sw = new StreamWriter(SPAuthhttpWebRequest.GetRequestStream()))
    {
        sw.Write(postData);
    }
     
    //Getting the Respose and reading the result.
    HttpWebResponse SPLoginResponse = SPAuthhttpWebRequest.GetResponse() as  HttpWebResponse;
    string ret = SPLoginResponse.Cookies["FedAuth"].Value;
    return ret;
 
}

 HTML Scraping methods to get ViewSate and EventValidation for post back to SharePoint forms login page

private string  ExtractViewState(string s)
{
    string viewStateNameDelimiter = "__VIEWSTATE";
    string valueDelimiter = "value=\"";
 
    int viewStateNamePosition = s.IndexOf(viewStateNameDelimiter);
    int viewStateValuePosition = s.IndexOf(
          valueDelimiter, viewStateNamePosition
       );
 
    int viewStateStartPosition = viewStateValuePosition +
                                 valueDelimiter.Length;
    int viewStateEndPosition = s.IndexOf("\"", viewStateStartPosition);
 
    return HttpUtility.UrlEncodeUnicode(
             s.Substring(
                viewStateStartPosition,
                viewStateEndPosition - viewStateStartPosition
             )
          );
}
 
private string  ExtractEventValidation(string s)
{
    string EventValidationDelimiter = "__EVENTVALIDATION";
    string valueDelimiter = "value=\"";
 
    int EventValidationPosition = s.IndexOf(EventValidationDelimiter);
    int EventValidationValuePosition = s.IndexOf(
          valueDelimiter, EventValidationPosition
       );
 
    int EventValidationStartPosition = EventValidationValuePosition +
                                 valueDelimiter.Length;
    int EventValidationEndPosition = s.IndexOf("\"", EventValidationStartPosition);
 
    return HttpUtility.UrlEncodeUnicode(
             s.Substring(
                EventValidationStartPosition,
                EventValidationEndPosition - EventValidationStartPosition
             )
          );
}

Step 2

Store the FedAuth Cookie in Session for reuse by client object model

protected void  LoginUser_LoggingIn(object sender, LoginCancelEventArgs e)
{
    //  URL to the SharePoint Forms Login Page
    String SiteLogin="https://spsandbox.acwest.com/_forms/default.aspx";
 
    try
    {
        String AuthToken = getAuthToken(SiteLogin, LoginUser.UserName, LoginUser.Password);
        Session["AuthToken"] = AuthToken;
    }
    catch (Exception ex)
    {
 
    }
}

 

Step 3

Now lets use the client object model in a simple aspx page to talk to our FBA SharePoint Site.

using Microsoft.SharePoint.Client;
 
namespace ....
{
    public partial  class _Default : System.Web.UI.Page
    {
        protected void  txtGetSPList_Click(object sender, EventArgs e)
        {
            String SPSITEURL = "https://my.fbasharepointsite.com/";
            ClientContext ctx = new  ClientContext(SPSITEURL);
 
            //use default credentials
            ctx.Credentials = CredentialCache.DefaultCredentials;
 
            //configure the handler that will pick up the auth cookie
            ctx.ExecutingWebRequest += new  EventHandler<WebRequestEventArgs>(ctx_ExecutingWebRequest);
 
            //get the web
            Web w = ctx.Web;
 
            //LOAD LISTS WITH ALL PROPERTIES
            var lists = ctx.LoadQuery(w.Lists);
 
            //execute the query
            ctx.ExecuteQuery();
 
 
            foreach (List theList in lists)
            {
                Console.WriteLine(theList.Title);
            }
        }
 
        void ctx_ExecutingWebRequest(object sender, WebRequestEventArgs e)
        {
            //TAKE OUT AND PARAMETERIZE
            String SPSITEURL = "https://my.fbasharepointsite.com/";
            try
            {
                    System.Net.CookieContainer cc = new  System.Net.CookieContainer();
                    Cookie SPAuthCookie = new  Cookie("FedAuth", Session["AuthToken"].ToString());
                    SPAuthCookie.Expires = DateTime.Now.AddHours(1);
                    SPAuthCookie.Path = "/";
                    SPAuthCookie.Secure = true;
                    SPAuthCookie.HttpOnly = true;
                    Uri formsUri = new  Uri(SPSITEURL);
                    SPAuthCookie.Domain = formsUri.Host;
                    cc.Add(SPAuthCookie);
                    e.WebRequestExecutor.WebRequest.CookieContainer = cc;    
            }
            catch (Exception ex)
            {
 
            }
        }

This should return a list of all the lists on the site.

Follow-up

This code needs error handling added as well as better organizations into say a helper library.  Additional parameterization of things like SharePoint Urls and the post variables to make it more flexible would also be nice.  The idea here was to give you the general idea on how to accomplish the task.  Depending on the configuration of your SharePoint site it might also be required to add code to refresh the FedAuth Cookie stored in Session.  Otherwise the cookie may expire and the value you stored in session will be useless.

References