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:
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:
class SPTasksList
{SPAuthenticationHelper _authenticationHelper;
ListsSoapClient _listsClient;
bool isBusy = false;
TaskItem currentUpdate = null;
string _taskListUri = "http://spsnh.sharepoint.com/TeamSite/Lists/Tasks/AllItems.aspx";
public SPTasksList()
{_authenticationHelper = new SPAuthenticationHelper(_taskListUri);
_listsClient = new ListsSoapClient();
_listsClient.GetListItemsCompleted += new EventHandler<GetListItemsCompletedEventArgs>(_listsClient_GetTasksListCompleted);
_listsClient.UpdateListItemsCompleted += new EventHandler<UpdateListItemsCompletedEventArgs>(_listsClient_UpdateListItemsCompleted);
}
public void
BeginGetTasksList()
{if (!_authenticationHelper.IsAuthenticated)
{
_authenticationHelper.OnAuthenticated += new EventHandler<EventArgs>(_authenticationHelper_OnAuthenticated_GetTasks);
_authenticationHelper.SigninAsync(Configuration.UserName, Configuration.Password);
}
else if (!isBusy)
{
isBusy = true;
XElement query = XElement.Parse("
string ListName = "Tasks";
string ViewId = "{f717e507-7c6e-4ece-abf2-8e38e0204e45}";
_listsClient.GetListItemsAsync(ListName, ViewId, query, null, null, null, null);
}
}
void
_authenticationHelper_OnAuthenticated_UpdateTask(object
sender, EventArgs e)
{_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;
using System.Net;using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
namespace SPSNH_SPConnector.Implementation
{
public class SPAuthenticationHelper
{
public CookieContainer Cookies { get; set; }
public bool IsAuthenticated { get; private set; }
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 = @"<s:Envelope xmlns:s=""http://www.w3.org/2003/05/soap-envelope"" xmlns:a=""http://www.w3.org/2005/08/addressing"" xmlns:u=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd""> <s:Header> <a:Action s:mustUnderstand=""1"">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action> <a:ReplyTo> <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address> </a:ReplyTo> <a:To s:mustUnderstand=""1"">https://login.microsoftonline.com/extSTS.srf</a:To> <o:Security s:mustUnderstand=""1"" xmlns:o=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd""> <o:UsernameToken> <o:Username>{0}</o:Username> <o:Password>{1}</o:Password> </o:UsernameToken> </o:Security> </s:Header> <s:Body> <t:RequestSecurityToken xmlns:t=""http://schemas.xmlsoap.org/ws/2005/02/trust""> <wsp:AppliesTo xmlns:wsp=""http://schemas.xmlsoap.org/ws/2004/09/policy""> <a:EndpointReference> <a:Address>{2}</a:Address> </a:EndpointReference> </wsp:AppliesTo> <t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType> <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType> <t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType> </t:RequestSecurityToken> </s:Body> </s:Envelope>";
Uri _uri;
HttpWebRequest _getTokenRequest = HttpWebRequest.CreateHttp(_authUrl);
HttpWebRequest _submitTokenRequest = null;
string _token;
public SPAuthenticationHelper(string uri)
{
_uri = new Uri(uri);
Cookies = new CookieContainer();
}
public void SigninAsync(string userName, string password)
{
if (!_isAuthenticationInProgress)
{
_isAuthenticationInProgress = true;
getTokenAsync(userName, password);
}
}
private void getTokenAsync(string userName, string password)
{
string tokenRequestXml = string.Format(_samlXml, userName, password, _uri.Host);
_getTokenRequest.Method = "POST";
_getTokenRequest.BeginGetRequestStream(new AsyncCallback(Get_GetToken_RequestStreamCallback), tokenRequestXml);
}
private void Get_GetToken_RequestStreamCallback(IAsyncResult result)
{
string tokenRequestXml = (string)result.AsyncState;
var reqstream = _getTokenRequest.EndGetRequestStream(result);
using (StreamWriter w = new StreamWriter(reqstream))
{
w.Write(tokenRequestXml);
w.Flush();
}
_getTokenRequest.BeginGetResponse(new AsyncCallback(Get_GetToken_ResponseStreamCallback), null);
}
private void Get_GetToken_ResponseStreamCallback(IAsyncResult result)
{
_token = null;
var response = _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 = new UriBuilder(_uri.Scheme, _uri.Host, _uri.Port);
_submitTokenRequest = HttpWebRequest.CreateHttp(bldr.Uri + _login);
_submitTokenRequest.CookieContainer = Cookies;
_submitTokenRequest.Method = "POST";
_submitTokenRequest.BeginGetRequestStream(new AsyncCallback(Get_SubmitToken_RequestStreamCallback), null);
}
private void Get_SubmitToken_RequestStreamCallback(IAsyncResult result)
{
var requestStream = _submitTokenRequest.EndGetRequestStream(result);
using (StreamWriter w = new StreamWriter(requestStream))
{
w.Write(_token);
w.Flush();
}
_submitTokenRequest.BeginGetResponse(new AsyncCallback(Get_SubmitToken_ResponseCallback), null);
}
private void Get_SubmitToken_ResponseCallback(IAsyncResult result)
{
UriBuilder bldr = new UriBuilder(_uri.Scheme, _uri.Host, _uri.Port);
var response = _submitTokenRequest.EndGetResponse(result);
string responseString = (new StreamReader(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);
}
}
}
}