[Edit 7/8/2013: I’ve tweaked this to note that this was for WP7. WP8 has a whole new set of SDKs for SP integration, and it’s a different, and much easier story to use them.]
As promised from my presentation at SharePoint Saturday New Hampshire, I owe a code listing on the meaty part of the chat… the Office 365 authentication component, especially. It allows a Windows Phone Silverlight app to access things the lists.asmx service behind the Windows Live ID authentication. (Frankly, the technique is the same, no matter what kind of client you’re using, but the demo I was doing was using Silverlight 4 for Windows Phone 7.
I also owe slides:
[office src=”https://r.office.microsoft.com/r/rlidPowerPointEmbed?p1=1&p2=1&p3=SD90A564D76FC99F8F!274&p4=&ak=!ABO7SzieOkx6gtY&kip=1″ width=”402″ height=”327″]
Here’s the activity rundown:
1) The client app (“Windows Phone App”) makes a SAML SOAP request to https://login.microsoftonline.com/extSTS.srf
2) The SAML response comes back, allowing the app to parse the SAML token.
3) Make another call, this time to {your Office365 team site}/_forms/default.aspx?wa=wsignin1, posting the token.
4) The response that comes back need only be checked for errors, the magic is in the cookie container. It contains an HTTPOnly token (which the development tools do a terribly good job of hiding.)
5) Assign your cookie container from your previous result to the ListSoapClient that you’re using to make your service calls from.
6) Profit!
I broke up the “Activation” line on the client side to point out that the calls are Async.
In any case, I have a very rough SPAuthenticationHelper class that I also promised to post.
Here’s an example of how you can use it:
{
SPAuthenticationHelper _authenticationHelper;
ListsSoapClient _listsClient;
bool isBusy = false;
string _taskListUri = “http://spsnh.sharepoint.com/TeamSite/Lists/Tasks/AllItems.aspx”;
{
_authenticationHelper = new SPAuthenticationHelper(_taskListUri);
_listsClient = new ListsSoapClient();
_listsClient.GetListItemsCompleted += newEventHandler<GetListItemsCompletedEventArgs>(_listsClient_GetTasksListCompleted);
_listsClient.UpdateListItemsCompleted += newEventHandler<UpdateListItemsCompletedEventArgs>(_listsClient_UpdateListItemsCompleted);
}
{
if (!_authenticationHelper.IsAuthenticated)
{
_authenticationHelper.OnAuthenticated += newEventHandler<EventArgs>(_authenticationHelper_OnAuthenticated_GetTasks);
_authenticationHelper.SigninAsync(Configuration.UserName, Configuration.Password);
}
else if (!isBusy)
{
isBusy = true;
XElement query = XElement.Parse(“Completed”);
string ListName = “Tasks”;
string ViewId = “{f717e507-7c6e-4ece-abf2-8e38e0204e45}”;
_listsClient.GetListItemsAsync(ListName, ViewId, query, null, null, null, null);
}
}
{
_listsClient.CookieContainer = _authenticationHelper.Cookies;
BeginUpdateTask(currentUpdate);
}
I ported this from a few other examples I found online to Silverlight for Windows Phone. I apologize, I haven’t had time to polish it, and I’m having a hard time with the embedded SOAP litteral, but here’s the SPAuthenticationHelper class:
using System.Net;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
namespace SPSNH_SPConnector.Implementation
{
public class SPAuthenticationHelper
{
public CookieContainerCookies { get; set; }
public boolIsAuthenticated { get; privateset; }
public event EventHandler<EventArgs> OnAuthenticated;
private bool_isAuthenticationInProgress = false;
const string_authUrl=“https://login.microsoftonline.com/extSTS.srf”;
const string _login=“/_forms/default.aspx?wa=wsignin1.0”;
//namespaces in the SAML response
const string _nsS = “http://www.w3.org/2003/05/soap-envelope”;
const string _nswst = “http://schemas.xmlsoap.org/ws/2005/02/trust”;
const string _nswsse = “http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd”;
const string _nswsu = “http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd”;
const string _nswsa = “http://www.w3.org/2005/08/addressing”;
const string _nssaml = “urn:oasis:names:tc:SAML:1.0:assertion”;
const string _nswsp = “http://schemas.xmlsoap.org/ws/2004/09/policy”;
const string _nspsf = “http://schemas.microsoft.com/Passport/SoapServices/SOAPFault”;
const string _samlXml =@” “; http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue http://www.w3.org/2005/08/addressing/anonymous https://login.microsoftonline.com/extSTS.srf {0} {1} {2} http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey http://schemas.xmlsoap.org/ws/2005/02/trust/Issue urn:oasis:names:tc:SAML:1.0:assertion
Uri _uri;
HttpWebRequest _getTokenRequest = HttpWebRequest.CreateHttp(_authUrl);
HttpWebRequest _submitTokenRequest = null;
string _token;
public SPAuthenticationHelper(string uri)
{
_uri = new Uri(uri);
Cookies = new CookieContainer();
}
public voidSigninAsync(string userName, string password)
{
if (!_isAuthenticationInProgress)
{
_isAuthenticationInProgress = true;
getTokenAsync(userName, password);
}
}
private void getTokenAsync(stringuserName, string password)
{
string tokenRequestXml = string.Format(_samlXml, userName, password, _uri.Host);
_getTokenRequest.Method = “POST”;
_getTokenRequest.BeginGetRequestStream(newAsyncCallback(Get_GetToken_RequestStreamCallback), tokenRequestXml);
}
private voidGet_GetToken_RequestStreamCallback(IAsyncResultresult)
{
string tokenRequestXml = (string)result.AsyncState;
var reqstream = _getTokenRequest.EndGetRequestStream(result);
using (StreamWriterw = new StreamWriter(reqstream))
{
w.Write(tokenRequestXml);
w.Flush();
}
_getTokenRequest.BeginGetResponse(new AsyncCallback(Get_GetToken_ResponseStreamCallback), null);
}
private voidGet_GetToken_ResponseStreamCallback(IAsyncResultresult)
{
_token = null;
varresponse = _getTokenRequest.EndGetResponse(result);
var xDoc = XDocument.Load(response.GetResponseStream());
var body=xDoc.Descendants(XName.Get(“Body”, _nsS)).FirstOrDefault();
if (body != null)
{
var fault = body.Descendants(XName.Get(“Fault”, _nsS)).FirstOrDefault();
if (fault != null)
{
var error=fault.Descendants(XName.Get(“text”, _nspsf)).FirstOrDefault();
if (error != null)
throw new Exception(error.Value);
}
else
{
var token = body.Descendants(XName.Get(“BinarySecurityToken”, _nswsse)).FirstOrDefault();
if (token != null)
{
_token = token.Value;
SubmitTokenAsync();
}
}
}
}
private void SubmitTokenAsync()
{
UriBuilder bldr = newUriBuilder(_uri.Scheme, _uri.Host, _uri.Port);
_submitTokenRequest = HttpWebRequest.CreateHttp(bldr.Uri + _login);
_submitTokenRequest.CookieContainer = Cookies;
_submitTokenRequest.Method = “POST”;
_submitTokenRequest.BeginGetRequestStream(newAsyncCallback(Get_SubmitToken_RequestStreamCallback), null);
}
private voidGet_SubmitToken_RequestStreamCallback(IAsyncResultresult)
{
var requestStream = _submitTokenRequest.EndGetRequestStream(result);
using (StreamWriterw = new StreamWriter(requestStream))
{
w.Write(_token);
w.Flush();
}
_submitTokenRequest.BeginGetResponse(newAsyncCallback(Get_SubmitToken_ResponseCallback), null);
}
private voidGet_SubmitToken_ResponseCallback(IAsyncResultresult)
{
UriBuilder bldr = newUriBuilder(_uri.Scheme, _uri.Host, _uri.Port);
varresponse = _submitTokenRequest.EndGetResponse(result);
string responseString = (newStreamReader(response.GetResponseStream())).ReadToEnd();
bldr.Path = null;
Cookies = _submitTokenRequest.CookieContainer;//.GetCookies(bldr.Uri);
_isAuthenticationInProgress = false;
IsAuthenticated = true;
if (OnAuthenticated != null)
{
EventArgs args = new EventArgs();
OnAuthenticated(this, args);
}
}
}
}