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
- Customize ASP.NET forms auth page to add the back channel calls to SharePoint to get a SharePoint AuthCookie.
- Store the SharePoint AuthCookie in Session Variable
- 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
- Using the Client Object Model with a Forms Based Auth Site in SharePoint 2010 by Steve Peschka
- This blog and sample code provides the a simple method to access a SharePoint Site via Client OM and forms auth
- Using the Client Object Model with a Claims Based Auth Site in SharePoint 2010 by Steve Peschka
- This blog and sample code provided some additional insight into SharePoint Claims authentication
- Fiddler Web Debugging Proxy
- This is a fantastic tool for web administrators looking to debug web stie issues as well as for developers to learn what actually goes on under the covers with your HTTP requests