Quantcast LiveSide - Windows Live news and interviews
LiveSide on the Windows Live Network LiveSide on Facebook LiveSide on Twitter LiveSide RSS  
Translate this page
MoFuse
LiveSide - Developer Blog

Windows Live Web Toolbar Mashup – MessyTwit

A lot of people nowadays have Twitter accounts, a Facebook account etc. and if you’re reading this then I’m also assuming you have a Windows Live account and some interest in Windows Live Services. Each of these have their own “Personal Message” where you can tell people what you are currently doing and there-in lies the problem. You have to visit each of these places to update them individually. Why not have a central place where you can update them all?

The good news is that you can. There are various ways of doing this, whether through a website or a Silverlight application. Basically anything that can use web services. With Microsoft’s recent introduction of the Windows Live Web Toolbar, I thought I’d do just that and show you how easy it is.  I will be building upon my previous articles regarding the new Windows Live controls so please go check them out if you haven’t already as I’ve discussed a lot of the code present in this mashup in previous articles.

So first up, lets take a look at our actual web page :-

MessyTwitBlank

Here you can see that I’ve sectioned off the page. At the top of the page we will display the users Windows Live login picture along with their personal message that is tied to that account. Just below that will be the list of their contacts. This is an easy way to display the personal messages of their Windows Live contacts. The next section will be their Twitter account showing their own personal messages that they have sent to Twitter and at the bottom you will see the Windows Live Web Toolbar that we will use to sign people into their Windows Live accounts and hook in with the other Windows Live Web Controls that we display on the page.

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:msgr="http://messenger.live.com/2009/ui-tags">
<head runat="server">
    <title>MessyTwit</title>
    <link type="text/css" rel="Stylesheet" href="MessyTwit.css" />
    <script type="text/javascript" src="http://www.wlmessenger.net/api/3.0/loader.js"></script>
    <script type="text/javascript" src="JScript/JQuery.js"></script>
    <script type="text/javascript" src="JScript/Messenger.js"></script>
    <script type="text/javascript" src="JScript/Twit.js"></script>
    <script type="text/javascript">
        Microsoft.Live.Core.Loader.load(['Messenger.UI', 'Messenger.UI.Styles.Core'], null);
    </script>
</head>
<body>
    <msgr:app 
            privacy-url="Privacy.html" 
            channel-url="Channel.htm" 
            application-verifier-token="<%= appVerifier %>" 
            token-url="RefreshMessengerToken.aspx"
            onauthenticated="onAuthenticated"></msgr:app> 
    <form id="form1" runat="server">
    <div id="msgrDisplayPic">
    </div>
    <hr />
    <div>
            <msgr:contact-list word-wheel-enabled="false"></msgr:contact-list>
        </div>
        <hr />
<%--    <div id="ctrls">
            <msgr:personal-message cid='$user' id='persmsg' editable='true'></msgr:personal-message>
    </div> 
--%>    
        <div id="msgs">
            <div id="TwitCreds" class="msgHidden">
                <span>Twitter Username&nbsp;&nbsp;</span>
                <input type="text" id="acct" /><br />
                <span>Twitter Password&nbsp;&nbsp;</span>
                <input type="password" id="pwd" /><br />
                <span id="TwitLogin">Login to Twitter</span>
            </div>
    </div>
    <hr />
        <div id="persMsg" class="msgHidden">
            <input type="text" id="msg" value="Enter a personal message" size="80" />
            <span id="msgsend">Send</span>
        </div>
    <div>
            <msgr:bar sign-in-enabled="true"></msgr:bar>
    </div> 
</form>
</body>
</html>

 

 

Above is the default.aspx page. As you can see, it’s not very complicated at all. Most of this was covered in my previous articles so I’ll just give a quick rundown here.

At the top we include various Javascript files which we’ll cover later and we tell the page to load the messenger controls when the xhtml page has loaded.

Next we have the messenger application section in which we supply various required variables such as where our privacy and channel pages are and we also set an event for when the user has authenticated (logged in to) their Windows Live Account.

After that there is a placeholder for where we will put the users display picture and Windows Live personal message. Following the horizontal rule (I’ve only put that in the display physically break the page into sections) we have the Windows Live Contact List control. It’s as simple as that one line of markup to display a very nice list of the users contacts.

The next section on the page is where we will gather the users’ Twitter credentials so that we can interact with their Twitter account.  Towards the bottom we have a simple input box that the user will type their personal message in to and finally we display the Windows Live Web Toolbar at the bottom of the page.

For completeness here is the code behind for the default.aspx page :-

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using WindowsLive;
using System.Diagnostics;
 
public partial class _Default : System.Web.UI.Page 
{
    WindowsLiveLogin wll = new WindowsLiveLogin(true);
    public string appVerifier
    {
        get
        {
            return wll.GetAppVerifier();
        }
    }
}

 

I’ve covered this before so won’t go into it here.

The corresponding style sheet associated with this page is very basic. Here is MessyTwit.css :-

input#msg
{
    border-width: 0px;
}
 
.msgHidden
{
    display: none;
}
 
.msgShow
{
    display: block;
}

 

 

We hide the border around the personal message input box and we define two classes to hide or show various elements of our page.

There are two main Javascript files that cover all of the functionality on the page. The Messenger.js file I’ve basically covered before :-

var userToken = null;
var msgrUser = null;
function onAuthenticated(e)
{
    msgrUser = e.get_user();
    msgrUser.add_signInCompleted(SignInCompleted);
}
function onUserConsentCompleted(e)
{
    userToken = e.get_consentToken();
}
function SignInCompleted(sender, e)
{
    if (e.get_resultCode() === Microsoft.Live.Messenger.SignInResultCode.success)
    {
        if (typeof (InvokeOnUserSignIn) === 'function')
        {
            InvokeOnUserSignIn();
        }
    }
    else
    {
        OnUserSignedOut();
    }
    var userAddress = msgrUser.get_address().get_address();
    var usercid = msgrUser.get_identity().get_cid();
    var signinframe = document.getElementById("msgrDisplayPic");
    var tag = Microsoft.Live.Messenger.UI.Tags.TagsFactory.createTag('display-picture', 
    { 'cid': usercid, 'presence-enabled': 'true', 'size': 'Large', 'logo-enabled': 'true' });
    $("#msgrDisplayPic").append(tag);
    var tag2 = Microsoft.Live.Messenger.UI.Tags.TagsFactory.createTag('personal-message', { 'cid': '$user', 'editable': 'false', 'id':'Persmsg2'});
    $("#msgrDisplayPic").append(tag2);
    $("div#TwitCreds").removeClass("msgHidden");
    $("div#TwitCreds").addClass("msgShow");
}
function OnUserSignedOut()
{
    //add code for sign-out.
}

 

 

As a quick overview we have the onAuthenticated event handler that we tied up in the messenger application block on the default.aspx page. As mentioned this gets fired when the user logs in to their Windows Live account. Once they login we capture that event and extract the user object then setup another event to capture when sign-in has been completed. The API fires off multiple events at differing stages of login. Another one of those events is when the user gives consent to send information back to our web site. In this event handler we simply capture the consent token that is sent.

Once the user has completed sign-in we make sure that sign-in was successful (i.e. the correct username and password were supplied). Next we create the users display picture and also their personal message. These are two different Windows Live controls.

The documentation for the Personal Message control says that it must have the CID of the user in order to display their personal message. This makes sense. However if you pass in the CID as we do for the display picture, the Personal Message control does not work. You HAVE to pass the $user string that is a reserved variable created by the Windows Live API.

Next we simply use some jQuery to insert these controls onto our page. We also now use some jQuery to show the Twitter credentials portion of our web page which is hidden until after the user signs in to their Windows Live account.

MessytwitAfterLogin

After the user has signed into their Windows Live account, this is what you will see. On this screenshot, my personal message is not displayed as I don’t have a personal message currently tied to this account however the control is present as you’ll see later.

The second of the two main Javascript files, twit.js, contains the jQuery functions for manipulating the page. This is all new so I’ll break it down a bit :-

$(document).ready
(
    function()
    {
        $('span#msgsend').click
    (
        function()
        {
            var acct = $("input#acct").val();
            var pwd = $("input#pwd").val();
            var msg = $("input#msg").val();

 

Everything in this Javascript file is done using event handlers. We attach the event handlers to the default.aspx document on ready. This is slightly different than onLoad. With onLoad you need to wait until the page has fully loaded, including the images that are displayed on the page. With jQuery’s ready handler the page has basically loaded, we have access to the page DOM, script etc. but we don’t have to wait until all the images are loaded.
So in the ready section we attach an event handler to the “send” text next to the personal message input. I just used text here but it could just have easily been a button.
When the text gets clicked we first get the username and password for the users Twitter account and we also get the personal message they want to send.
 
$.post("/MessyTwit/TwitterService.asmx/SendTweet",
{ 'account': acct, 'pass': pwd, 'msg': msg },

 

Next we have an AJAX call to a web service that we have created to send the personal to the users Twitter account.

 

function(_xml)
{
    $('div#msgs').text('');
    $xml = $(_xml);
    $xml.find("status").each
(
    function()
    {
        $('div#msgs').append("<div><img src='" + $(this).find("profile_image_url").text() + "'/>" +
        "<span>&nbsp;&nbsp;" + $(this).find('text').text() + "</span></div>");
    }
);
}
        , 'xml');

 

 

To finish off this AJAX call we define an anonymous function that gets called once the call returns to our script. Here we first blank out any Twitter messages that may already be displayed on the screen. Then we take the XML that is returned by the AJAX call and parse it. We loop through the XML parsing each individual message. For each message we take the display picture of the user and also the text of the message and display it in the placeholder we marked out in our default.aspx page.  The final part to this is just saying that the AJAX call will use XML rather than JSON or some other format.

        if (msgrUser)
        {
            msgrUser.get_presence().set_personalMessage(msg);
        }
 

So we have posted the users personal message to their Twitter account, next we need to do the same for their Windows Live account. In the code above, this is exactly what we do. We first check to see that the user object isn’t null and if it’s not then we simply call the set_personalMessage method to update their Windows Live personal message.

        $('input#msg').val('');
 
    }
);

 

Finally for this event handler we do some cleanup. Here I’m blanking out the personal message input box ready for their next message.

 

$('input#msg').focus
(
    function()
    {
        $(this).val('');
    }
);
 
$('input#msg').blur
(
    function()
    {
        if (this.value == '')
        {
            this.value = 'Enter your personal Message';
        }
    }
);
 

Here is the personal message input box before the user has clicked on it :-

MessyTwitInput1

and after they have clicked on it :-

Messytwitinput2

The next two event handlers we define are tied to the personal message input box. All that this does is blank the input box when the user focuses on it (clicks or tabs into it). If the user clicks or tabs away from it and the input box is blank then we insert the message “Enter your personal Message” into it so the user knows where to type.

        $('span#TwitLogin').click
        (
            function()
            {
                $('div#persMsg').removeClass('msgHidden');
                $('div#persMsg').addClass('msgShow');
                $('div#TwitCreds').removeClass('msgShow');
                $('div#TwitCreds').addClass('msgHidden');
                var acct = $("input#acct").val();
                $.post("/MessyTwit/TwitterService.asmx/GetTweet",
            { 'account': acct },
    function(_xml)
    {
        $xml = $(_xml);
        $xml.find("status").each
    (
        function()
        {
            $('div#msgs').append("<div><img src='" + $(this).find("profile_image_url").text() + "'/>" +
            "<span>&nbsp;&nbsp;" + $(this).find('text').text() + "</span></div>");
        }
    );
    }
            , 'xml');
            }
        );
 
    }
);

 

 

The final event handler is tied to the text I’ve displayed on the page for logging in to their Twitter account. Again, I’ve just used some text but it could just as well be a button control.

When the user clicks on the login text we hide the twitter login credentials area and display the personal message input box. Next we get the users username and again make an AJAX call. This time only to retrieve the users’ own Twitter messages. Finally we use the same anonymous function as we had above to parse through the returned XML and extract the display picture and message from each message returned and display them onscreen. As soon as the user enters their Twitter credentials you should see the following :-

MessyTwitTwitSignin

Now you can take this code and expand on it so that when the user clicks on the login text you actually do a check against their Twitter credentials and display an error message if they are not correct. Twitter has lots of web service calls that you can use to make this code more robust. See the Twitter API for more details.

This really only leaves one piece left, our web service :-

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Xml;
using System.Xml.Linq;
using System.IO;
using System.Net;
 
/// <summary>
/// Summary description for TwitterService
/// </summary>
[WebService(Namespace = "blah")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
[System.Web.Script.Services.ScriptService]
public class TwitterService : System.Web.Services.WebService {
 
    public TwitterService () {
 
        //Uncomment the following line if using designed components 
        //InitializeComponent(); 
    }
 
    [WebMethod]
    public XmlDocument GetTweet(string account) 
        {
            string url = "http://www.twitter.com/statuses/user_timeline/" + account + ".xml?count=5";
            HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(url);
            Request.Method = "GET";
            WebResponse Response = Request.GetResponse();
            StreamReader Reader = new StreamReader(Response.GetResponseStream());
            string Result = Reader.ReadToEnd();
            Reader.Close();
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(Result);
            return doc;
    }
 
        [WebMethod]
        public XmlDocument SendTweet(string account, string pass, string msg)
        {
            string url = "http://www.twitter.com/statuses/update.json";
            NetworkCredential creds = new NetworkCredential(account, pass);
            string EncodedText = "status=" + HttpUtility.UrlEncode(msg);
            HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(url);
            Request.Method = "POST";
            Request.Credentials = creds;
            Request.ContentType = "application/x-www-form-urlencoded";
            Request.ContentLength = EncodedText.Length;
            Request.UserAgent = "MessyTwit";
            Request.Timeout = 10000;
 
            System.Net.ServicePointManager.Expect100Continue = false;
 
            Stream reqStream = Request.GetRequestStream();
            StreamWriter Writer = new StreamWriter(reqStream);
            Writer.Write(EncodedText);
            Writer.Close();
 
            WebResponse Response = Request.GetResponse();
            StreamReader Reader = new StreamReader(Response.GetResponseStream());
            string Results = Reader.ReadToEnd();
 
            Reader.Close();
            Response.Close();
            reqStream.Close();
            XmlDocument doc = GetTweet(account);
            return doc;
        }
}
 

 

This is a standard .Net web service except that we need to un-comment the line that reads :-

[System.Web.Script.Services.ScriptService]

 

 

This is so that our Javascript can actually make calls to it. The first method that we have simply calls the Twitter web service and gets the top 5 messages the user has posted and returns that as XML back to our AJAX call.

The second method is the one that actually posts the message to Twitter. After we have posted our message to using the Twitter API we make the call to the first method to retrieve the new updated top 5 messages. The main line to take note of in this method is :-

System.Net.ServicePointManager.Expect100Continue = false;

 

 

Without this line any posts to Twitter will fail with a 417 return code. This only applies to posts, not to gets.

And there you have it. One web page that updates both a users’ Windows Live personal message and also their Twitter account as can be seen in the screenshot below. The Windows Live Personal-Message control now contains my latest message at the top of the page and also my latest Twitter entry has the same message :-

MessyTwitFinal

This should give you an idea of the kind of mashups that you can do using the new Windows Live Messenger UI controls. I’ve expanded this example to include Facebook although I didn’t put the code here but it’s fairly easy to do and the above code will give you all the basics you need. You can expand it to include any other site that does some kind of personal message you wish.

One final point should be noted. The Windows Live Web Toolbar includes it’s own area in which a user can type their personal message. When the personal message gets posted you can capture the event using the following :-

// attach event for future changes
user.get_presence().add_propertyChanged(function (sender, e) {
  if (e.get_propertyName() == "PersonalMessage")  {
    var msg = user.get_presence().get_personalMessage();
  }
});

This also applies to the Personal Message UI control if you allow it to be edited. This way you can handle whether the user types their personal message into the Web Toolbar or using a control that you’ve specifically placed on the page.


Windows Live UI Controls

Following on from my previous tutorial on how to integrate the Windows Live Web Bar into your site, we’ll build upon this and show you some of the other UI controls that you can use in your site.

One thing that a lot of people might want to do is display the signed-on users’ display picture somewhere on the site other than in the web-bar.

MessytwitDisplayPic

As with most everything Microsoft does in the web space there are a couple of ways of doing this. Lets start by doing it the easy way. Take the default.aspx page from the last tutorial and add a new <div> element under the <form> tag :-

<form id="form1" runat="server">
<div id="msgrDisplayPic"></div>

 

 

Into the div tag we will add the display picture UI control. This control has a few different options you can specify but only one is actually required, the cid of the user who’s image you wish to display. How do you get the cid of the signed-in user? Again there are a couple of ways of doing this but we’ll take the easy route again. For use with their tags on the aspx page, Microsoft has thoughtfully provided a variable that automatically gets populated with the users’ cid, $user. So the base tag that we need to enter into our div looks like this :-

<msgr:display-picture cid='$user'></msgr:display-picture>

 

 

When you run the page you’ll notice the default “blank” user image as nobody has signed in yet :-

MessytwitDisplayPic1

When a user signs in then this image will change to the users’ display picture as seen above.

There are a couple of options that you can add to the control. You can adjust it’s size by supplying it with a size attribute. Size can take three pre-defined values, Small, Medium and Large. By default the control is set to Medium size. You can also tell it whether to enable or disable presence information (the “glow” around the border of the image). By default this is set to true so presence information is displayed. Finally you can also state whether a logo is displayed in the bottom right of the image or not by setting the logo-enabled attribute. The default is set to false. When set to true, a small logo is displayed :-

messytwitDisplayPic3

So here is our final display-picture tag :-

<msgr:display-picture cid='$user' presence-enabled='true' size='Large' logo-enabled='true'></msgr:display-picture>

 

 

 

Microsoft also allow you to override their default style sheets for this control giving you a range of options :-

  • DisplayPictureControl: The outermost container of the display-picture tag.
  • DisplayPictureControl Border: Specified when presence is enabled.
  • DisplayPictureControl Large: Specified when the size is "large".
  • DisplayPictureControl Medium: Specified when the size is "medium".
  • DisplayPictureControl Small: Specified when the size is "small".
  • DisplayPictureControl_Icon: The logo if the logo is enabled.
  • DisplayPictureControl_Image: The display picture.
  • DisplayPictureControl_Image Online: Specified when the status is Online.
  • DisplayPictureControl_Image BeRightBack: Specified when the status is Be Right Back.
  • DisplayPictureControl_Image Busy: Specified when the status is Busy.
  • DisplayPictureControl_Image Away: Specified when the status is Away.
  • DisplayPictureControl_Image InACall: Specified when the status is In A Call.
  • DisplayPictureControl_Image OutToLunch: Specified when the status is Out To Lunch.
  • DisplayPictureControl_Image AppearOffline: Specified when the status is Appear Offline.
  • DisplayPictureControl_Image Offline: Specified when the status is Offline.

And that’s it, you can now add the users’ display picture to anywhere on your site with one tag.

If you want more fine grained control over the process or you wish to do all this programmatically Microsoft also allows you to do this.

First remove the <msgr:display-picture> tag from the default.aspx, but keep the <div> tag in place. Next create a Javascript file and add a reference to it at the top of the default.aspx :-

<script type="text/javascript" src="JScript/Messenger.js"></script>

 

In this Javascript file we will programmatically insert the users’ display picture control onto the site. I’ve added a couple of extra event handlers to this file that you don’t necessarily need but are very handy to have as well. First off we want to declare a couple of global variables, one to hold the users’ token that gets generated when they give their consent to sign-in to your site, and the second is to hold the actual user object :-

var userToken = null;
var msgrUser = null;

 

 

Our first event handler gets fired once the user gives his permission to sign into your site :-

function onUserConsentCompleted(e)
{
    userToken = e.get_consentToken();
}

 

 

All that we’re doing here is getting the consent token that gets generated and storing it in our global variable for use later.

The next event handler gets fired once the user has authenticated (i.e. the windows live credentials are correct) :-

function onAuthenticated(e)
{
    msgrUser = e.get_user();
    msgrUser.add_signInCompleted(SignInCompleted);
}

 

 

Here we capture the actual user object (the most important object you use when dealing programmatically with Windows Live Web Messenger) and store it in our global variable. Then we add an event handler for when the sign-in process has completed.

function SignInCompleted(sender, e)
{
    if (e.get_resultCode() === Microsoft.Live.Messenger.SignInResultCode.success)
    {
        if (typeof (InvokeOnUserSignIn) === 'function')
        {
            InvokeOnUserSignIn();
        }
    }
    else
    {
        OnUserSignedOut();
    }
    var usercid = msgrUser.get_identity().get_cid();
    var signinframe = document.getElementById("msgrDisplayPic");
    var tag = Microsoft.Live.Messenger.UI.Tags.TagsFactory.createTag('display-picture', 
    { 'cid': usercid, 'presence-enabled': 'true', 'size': 'Large', 'logo-enabled': 'true' });
    signinframe.appendChild(tag);
}

 

 

The SignInCompleted handler first of all checks to see if authorization was correct (did the user really sign in or did they perhaps have a typo and sign-in wasn’t completed successfully). If sign-in was not correct then we make sure our page reflects that by calling our sign-out routine which should house any clean-up code.

If the user has successfully signed in then we grab their cid. Remember, this is the one required field for the display-picture control (and most other controls as well). Next we get a reference to our <div> element where we want to place the display-picture control. All Windows Live Messenger UI Controls are instantiated through the TagFactory object. Here we simply call the method createTag and pass into it two values. The first is the type of control that we wish to create, in this case the display-picture control. The second value is a set of key:value pairs containing any attributes that we wish to supply. In the example code above we are simply re-creating the <msgr:display-picture> tag we created earlier on the page itself. The only difference here is that we are passing in the cid manually. Finally we simply add this control into the <div> tag we got a reference for earlier.

The final entry in our Javascript file is the event handler for when the user signs out :-

function OnUserSignedOut()
{
    //add code for sign-out.
}

 

And that’s it, you will end up with exactly the same screen as we did earlier, only this time you have gained knowledge in how to do it programmatically.

The basics on what we have covered here applies to all the other Web Messnger UI controls. For example, if we wanted to add the contact-list control to our page we simply add the appropriate tag within our page or do it programmatically :-

<msgr:contact-list word-wheel-enabled="false"></msgr:contact-list>

 

 

And this results in the following :-

MessytwitContacts

There are a lot of UI controls to explore. You can find more information about what’s available, their methods and parameters etc. over on msdn :- http://msdn.microsoft.com/en-us/library/microsoft.live.messenger.ui.tags.aspx


Windows Live Web Toolbar – How to integrate into your site

A few months ago at Mix 09, the Windows Live Team launched a number of exciting new additions under the Windows Live Web Toolkit. One of which was the Windows Live Web Toolbar. This toolbar allows you include presence information into your site as well as other social experiences.

In this tutorial I will show you just how easy it is to incorporate the Web Toolbar into your own site.

First of all, you need to have access to the Windows Azure development portal. In order to use the Web Toolbar you need to register with Azure and receive an application ID and secret Key :-

MessyTwitAzure

When you sign into Azure, you will first be presented with a list of your projects. At the top left you will see a link for “New Project”. Click on this link. :-

MessyTwitAzureNew

On this screen you will be presented with four options relating to the different types of projects that you will be programming.  The one we are interested in is the one at the bottom “Live Services Existing APIs”.  Click on this to create a new Live Services application registration :-

 

MessyTwitAzureNew2

You will be asked to give your project a name and a description as well as a valid domain from which it will run and also a return URL. The return URL is where Windows Live Services will return to your application after it has completed login (for the purposes of this application I just gave a return url of {domain}/default.aspx. Once you have entered this information click the “create” button to create your registration information :-

MessytwitAzureAuth

On the final screen in Azure you will be given your Application ID and your secret key (blanked out from the screenshot) as well as confirming your domain and the return url.

Now that you have registered your application you can fire up Visual Studio and create a new website.  There are a few things that you need to include in your site for everything to work correctly.  First off you need to have a Privacy.htm page and a Channel.htm page. These can be the exact same as you’ve used in the past if you followed my Web Messenger tutorial. For those that didn’t, here is the Privacy.htm file :-

<html >
   <head>
   <title>Privacy Policy</title>
   <meta name="ROBOTS" content="NONE"/>
   </head>
   <body>
   <!-- The privacy policy for your Web site goes here. -->
   <div>No information shall be retained on this site.</div>
   </body>
</html>

 

 

This page contains your sites privacy policy.  Here is the Channel.htm file :-

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html >
<head>
<title>Channel</title>
<meta name="ROBOTS" content="NONE"/>
<script type="text/javascript">
function onLoad()
{
    try
    {
        var hash = window.location.hash.substr(1);
        if (window.location.replace == null)
        {
            window.location.replace = window.location.assign;
        }
        window.location.replace("about:blank");
        var name = hash.split("/")[0];
        var win = null;
        if (name && (name != ".parent"))
        {
            win = window.parent.frames[name];
        }
        else
        {
            win = window.parent.parent;
        }
        if (win.Microsoft)
        {
            win.Microsoft.Live.Channels.Mux._recv_chunk(hash);
        }
    }
    catch (ex)
    {
        /* ignore */
    }
}
</script>
</head>
<body onload="onLoad();"></body>
</html>

 

 

This file enables the shuttling of authorization information between Microsoft’s domain and your own. There are another couple of files that we will get to soon. 

The next step is to open web.config file and add the following to your appSettings section :-

<appSettings>
    <add key="wll_appid" value="{Application ID}"/>
    <add key="wll_secret" value="{Secret Code}"></add>
    <add key="wll_consenturl" value="http://consent.messenger.services.live.com/"/>
</appSettings>

 

The first value is the Application ID that Azure returned to you on the final screen and the second is the secret key also returned to you on the final Azure screen.  The third should be entered as is. It gives the consent information for the end user, which we’ll get onto shortly.

Now onto your Default.aspx page. There is actually very little code required in order to get this to run :-

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:msgr="http://messenger.live.com/2009/ui-tags">
<head runat="server">
    <title>MessyTwit</title>
    <script type="text/javascript" src="http://www.wlmessenger.net/api/3.0/loader.js"></script>
    <script type="text/javascript">
        Microsoft.Live.Core.Loader.load(['Messenger.UI', 'Messenger.UI.Styles.Core'], null);
    </script>
</head>
<body>
        <msgr:app 
            privacy-url="Privacy.html" 
            channel-url="Channel.htm" 
            application-verifier-token="<%= appVerifier %>" 
            token-url="RefreshMessengerToken.aspx"></msgr:app>
    <form id="form1" runat="server">
    <div>
            <msgr:bar></msgr:bar>
    </div>
    </form>
</body>
</html>

 

 

This is mainly just the standard aspx page that Visual Studio creates for you. To this you will add references to the new Windows Live 3.0 APIs and call the load method of Loader. This essentially brings down the relevant Javascript to the application and sets everything up for you.

<script type="text/javascript" src="http://www.wlmessenger.net/api/3.0/loader.js"></script>
<script type="text/javascript">
    Microsoft.Live.Core.Loader.load(['Messenger.UI', 'Messenger.UI.Styles.Core'], null);
</script>

 

 

It does this in an intelligent manner so that loading times are reduced as much as possible for the client. Also notice the html tag, we have added a link to an xml schema :-

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:msgr="http://messenger.live.com/2009/ui-tags">

 

 

This schema allows you to use <msgr:> tags within your html page and they will be understood. These msgr tags are the new Messenger UI controls that have shipped as part of the 3.0 API release.

Next we instantiate the messenger application through the <msgr:app> tag :-

<msgr:app 
    privacy-url="Privacy.html" 
    channel-url="Channel.htm" 
    application-verifier-token="<%= appVerifier %>" 
    token-url="RefreshMessengerToken.aspx"></msgr:app>

 

 

In this you supply the link to your privacy and channel pages (as detailed above), the application verifier token (this verifies that your application is valid – hence the registration against Azure to start this) and finally a link to a page that handles the Token that is passed back through Windows Live Services to your application.

<%@ Page Language="C#" %>
<%@ Import Namespace="WindowsLive" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<!--
    Copyright (c) Microsoft Corporation.  All rights reserved.
-->
 
<script runat="server">
    /*
    * Configuration:
    * The minimum requirement is to specify the application id and application secret in web.config
    * Refer to the boolean constructor of the WindowsLiveLogin for additional configuration options.
    */
 
    /// <summary>
    /// The name of the consent token cookie
    /// </summary>
    private const string ConsentTokenCookieName = "msgr-consent-token";
 
    /// <summary>
    /// The name of the delegation token cookie
    /// </summary>    
    private const string DelegationTokenCookieName = "msgr-delegation-token";
 
    /// <summary>
    /// Current consent token that is stored in the conset cookie
    /// </summary>
    private string consentToken = null;
 
    /// <summary>
    /// Provides token processing functionality
    /// </summary>
    private WindowsLiveLogin windowsLiveLogin;
 
    /// <summary>
    /// Occurs when the page loads
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
 
    private void Page_Load(object sender, EventArgs e)
    {               
        try
        {
            if (IsConsentCookieAvailable())
            {
                RefreshDelegatedAuthenticationCookie();
            }
        }
        catch (Exception ex)
        {
            HandleException(ex);
        }
    }
 
    /// <summary>
    /// Tests whether the consent token cookie is available
    /// </summary>
    /// <returns>'true' if the consent token cookie is available</returns>
    private bool IsConsentCookieAvailable()
    {
        HttpCookie consentTokenCookie = Request.Cookies[ConsentTokenCookieName];
 
        if (consentTokenCookie == null)
        {
            return false;
        }
 
        this.consentToken = consentTokenCookie.Value;
 
        return !string.IsNullOrEmpty(this.consentToken);
    }
 
    /// <summary>
    /// Refreshes the token
    /// </summary>
    private void RefreshDelegatedAuthenticationCookie()
    {
        this.windowsLiveLogin = new WindowsLiveLogin(true);        
        WindowsLiveLogin.ConsentToken token =
            this.windowsLiveLogin.ProcessConsentToken(this.consentToken);        
        bool expireDelegationToken = false;
 
        if (token == null)        
        {
            expireDelegationToken = true;
        }        
        else
        {
            if (!token.IsValid())
            {
                if (token.Refresh())
                {
                    UpdateDelegatedAuthenticationCookie(token);
                }
                else
                {
                    expireDelegationToken = true;
                }
            }
            else if (!IsDelegationTokenCookiePresent())
            {
                UpdateDelegatedAuthenticationCookie(token);
            }
        }
 
        if (expireDelegationToken)
        {
            ExpireDelegatedAuthenticatonCookie();
        }
    }
 
    /// <summary>
    /// Updates the cookies with a refreshed token
    /// </summary>
    /// <param name="token">The refreshed token</param>
    private void UpdateDelegatedAuthenticationCookie(WindowsLiveLogin.ConsentToken token)
    {
        HttpCookie delAuthCookie = new HttpCookie(DelegationTokenCookieName);
        delAuthCookie.Value = token.DelegationToken;
        delAuthCookie.Path = "/";
        Response.Cookies.Add(delAuthCookie);
 
        HttpCookie consentCookie = new HttpCookie(ConsentTokenCookieName);
        consentCookie.Value = token.Token;
        consentCookie.Path = "/";
        Response.Cookies.Add(consentCookie);
    }
 
    /// <summary>
    /// Expires the delegation token cookie
    /// </summary>
    private void ExpireDelegatedAuthenticatonCookie()
    {
        ExpireCookie(DelegationTokenCookieName);
    }
 
    /// <summary>
    /// Expires a given cookie
    /// </summary>
    /// <param name="cookieName">The name of the cookie to expire</param>
    private void ExpireCookie(string cookieName)
    {
        HttpCookie cookie = new HttpCookie(cookieName);
        cookie.Expires = DateTime.Now.AddYears(-1);
        cookie.Path = "/";
        Response.Cookies.Add(cookie);
    }
 
    /// <summary>
    /// Checks if the delegation token cookie is present
    /// </summary>
    /// <returns>'true' is returned if the delegation cookie is present</returns>
    private bool IsDelegationTokenCookiePresent()
    {
        HttpCookie cookie = Request.Cookies[DelegationTokenCookieName];
        return cookie != null && !string.IsNullOrEmpty(cookie.Value);
    }
 
    private void HandleException(Exception e)
    {
        // The application should log the exception as it may be caused due to a configuration issue
    }
 
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
 
    </div>
    </form>
</body>
</html>

 

 

Above is the RefreshMessengerToken.aspx page. This basically just handles the Token passed back to your application from Windows Live Services. Has the user given permission to be delegated on your site? Remove the cookie from the users machine when they sign-out etc. All fairly standard stuff.

Finally on your default.aspx page we have the magic tag :-

<msgr:bar></msgr:bar>

 

 

This is what actually adds the Messenger Web Bar to the bottom of your web site.  We’re very nearly done now.  The last thing we need to cover is the application verifier token. From the code in the Default.aspx page you can see that we used a server side variable, so here is the code-behind file for Default.aspx :-

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using WindowsLive;
 
public partial class _Default : System.Web.UI.Page 
{
    WindowsLiveLogin wll = new WindowsLiveLogin(true);
    public string appVerifier
    {
        get
        {
            return wll.GetAppVerifier();
        }
    }
}

 

 

Here we are creating a global variable that instantiates a WindowsLiveLogin object. Then the value of appVerifier is the returned value of GetAppVerifier method of the WindowsLiveLogin object. This basically reads your web.config file for the values we placed in there earlier and returns them. 

In Visual Studio, right click on your project and select “Add Asp.Net Folder” and add the App_Code folder to your project. Then place the following file called WindowsLiveLogin into that folder :-

/*
 * FILE:        WindowsLiveLogin.cs
 *                                                                      
 * DESCRIPTION: Sample implementation of Web Authentication and Delegated 
 *              Authentication protocol in C#. Also includes trusted 
 *              sign-in and application verification sample 
 *              implementations.
 *
 * VERSION:    1.1
 *
 * Copyright (c) 2008 Microsoft Corporation.  All Rights Reserved.
 */
 
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using System.Collections;
using System.Web;
using System.Web.Configuration;
using System.Security.Cryptography;
using System.IO;
using System.Net;
using System.Reflection;
using System.Xml;
 
namespace WindowsLive
{
    /// <summary>
    /// Sample implementation of Web Authentication and Delegated Authentication 
    /// protocol. Also includes trusted sign-in and application 
    /// verification sample implementations.
    /// </summary>
    public class WindowsLiveLogin
    {
        /// <summary>
        /// Stub implementation for logging debug output. You can run
        /// a tool such as 'dbmon' to see the output.
        /// </summary>
        static void debug(string msg)
        {
            System.Diagnostics.Debug.WriteLine(msg);
            System.Diagnostics.Debug.Flush();
        }
 
        /// <summary>
        /// Initialize the WindowsLiveLogin module with the
        /// application ID and secret key.
        ///
        /// We recommend that you employ strong measures to protect
        /// the secret key. The secret key should never be
        /// exposed to the Web or other users.
        /// </summary>
        public WindowsLiveLogin(string appId, string secret) : 
            this(appId, secret, null){}
 
        /// <summary>
        /// Initialize the WindowsLiveLogin module with the
        /// application ID, secret key, and security algorithm.
        ///
        /// We recommend that you employ strong measures to protect
        /// the secret key. The secret key should never be
        /// exposed to the Web or other users.
        /// </summary>
        public WindowsLiveLogin(string appId, string secret, string securityAlgorithm) :
            this(appId, secret, securityAlgorithm, false){}
 
        /// <summary>
        /// Initialize the WindowsLiveLogin module with the
        /// forceDelAuthNonProvisioned flag, policy URL, and return URL.
        /// 
        /// The 'force_delauth_nonprovisioned' flag indicates whether
        /// your application is registered for Delegated Authentication 
        /// (that is, whether it uses an application ID and secret key). We 
        /// recommend that your Delegated Authentication application always 
        /// be registered for enhanced security and functionality. 
        /// </summary>
        public WindowsLiveLogin(bool forceDelAuthNonProvisioned, string policyUrl, string returnUrl)
        {
            ForceDelAuthNonProvisioned = forceDelAuthNonProvisioned;
            PolicyUrl = policyUrl;
            ReturnUrl = returnUrl;
        }
 
        /// <summary>
        /// Initialize the WindowsLiveLogin module with the
        /// application ID, secret key, security algorithm and 
        /// forceDelAuthNonProvisioned flag.
        ///
        /// We recommend that you employ strong measures to protect
        /// the secret key. The secret key should never be
        /// exposed to the Web or other users.
        /// 
        /// The 'force_delauth_nonprovisioned' flag indicates whether
        /// your application is registered for Delegated Authentication 
        /// (that is, whether it uses an application ID and secret key). We 
        /// recommend that your Delegated Authentication application always 
        /// be registered for enhanced security and functionality. 
        /// </summary>
        public WindowsLiveLogin(string appId, string secret, string securityAlgorithm, bool forceDelAuthNonProvisioned) :
            this(appId, secret, securityAlgorithm, forceDelAuthNonProvisioned, null){}
 
        /// <summary>
        /// Initialize the WindowsLiveLogin module with the
        /// application ID, secret key, security algorithm,    
        /// forceDelAuthNonProvisioned and policy URL use.
        ///
        /// We recommend that you employ strong measures to protect
        /// the secret key. The secret key should never be
        /// exposed to the Web or other users.
        /// 
        /// The 'force_delauth_nonprovisioned' flag indicates whether
        /// your application is registered for Delegated Authentication 
        /// (that is, whether it uses an application ID and secret key). We 
        /// recommend that your Delegated Authentication application always 
        /// be registered for enhanced security and functionality. 
        /// </summary>
        public WindowsLiveLogin(string appId, string secret, string securityAlgorithm, bool forceDelAuthNonProvisioned, string policyUrl) :
            this(appId, secret, securityAlgorithm, forceDelAuthNonProvisioned, policyUrl, null){}
 
        /// <summary>
        /// Initialize the WindowsLiveLogin module with the
        /// application ID, secret key, security algorithm,    
        /// forceDelAuthNonProvisioned, policy URL and return URL.
        ///
        /// We recommend that you employ strong measures to protect
        /// the secret key. The secret key should never be
        /// exposed to the Web or other users.
        /// 
        /// The 'force_delauth_nonprovisioned' flag indicates whether
        /// your application is registered for Delegated Authentication 
        /// (that is, whether it uses an application ID and secret key). We 
        /// recommend that your Delegated Authentication application always 
        /// be registered for enhanced security and functionality.
        public WindowsLiveLogin(string appId, string secret, string securityAlgorithm, bool forceDelAuthNonProvisioned, string policyUrl, string returnUrl)
        {
            ForceDelAuthNonProvisioned = forceDelAuthNonProvisioned;
            AppId = appId;
            Secret = secret;
            SecurityAlgorithm = securityAlgorithm;
            PolicyUrl = policyUrl;
            ReturnUrl = returnUrl;
        }  
 
        /// <summary>
        /// Initialize the WindowsLiveLogin module from the
        /// web.config file if loadAppSettings is true. Otherwise,
        /// you will have to manually set the AppId, Secret and
        /// SecurityAlgorithm properties.
        /// 
        /// In a Delegated Authentication scenario, you may also specify
        /// the return and privacy policy URLs to use, as shown in the 
        /// Delegated Authentication samples.    
        /// </summary>
        public WindowsLiveLogin(bool loadAppSettings)
        {
            if (!loadAppSettings) { return; }
 
            NameValueCollection appSettings = WebConfigurationManager.AppSettings;
            if (appSettings == null) 
            {
                throw new IOException("Error: WindowsLiveLogin: Failed to load the Web application settings.");
            }
 
            string forceDelAuthNonProvisioned = appSettings["wll_force_delauth_nonprovisioned"];
 
            if (!string.IsNullOrEmpty(forceDelAuthNonProvisioned) && 
                (forceDelAuthNonProvisioned.ToLower() == "true"))
            {
                ForceDelAuthNonProvisioned = true;
            }
            else
            {
                ForceDelAuthNonProvisioned = false;
            }
 
            AppId = appSettings["wll_appid"];
            Secret = appSettings["wll_secret"];
            OldSecret = appSettings["wll_oldsecret"];
            OldSecretExpiry = appSettings["wll_oldsecretexpiry"];
            SecurityAlgorithm = appSettings["wll_securityalgorithm"];
            PolicyUrl = appSettings["wll_policyurl"];
            ReturnUrl = appSettings["wll_returnurl"];
            BaseUrl = appSettings["wll_baseurl"];
            SecureUrl = appSettings["wll_secureurl"];
            ConsentUrl = appSettings["wll_consenturl"];
        }
 
        /// <summary><![CDATA[
        /// Initialize the WindowsLiveLogin module from a settings file. 
        /// 
        /// 'settingsFile' specifies the location of the XML settings
        /// file containing the application ID, secret key, an optional
        /// security algorithm and a privacy policy URL (required for
        /// Delegated Auth).  The file is of the following format:
        /// 
        /// <windowslivelogin>
        ///   <appid>APPID</appid>
        ///   <secret>SECRET</secret>
        ///   <securityalgorithm>wsignin1.0</securityalgorithm>
        ///   <policyurl>http://[your domain]/[your privacy policy]</policyurl>
        ///   <returnurl>http://[your domain]/[your return url]</policyurl>
        /// </windowslivelogin>
        /// 
        /// In a Delegated Authentication scenario, you may also specify
        /// 'returnurl' and 'policyurl' in the settings file. 
        ///  
        /// We recommend that you store the Windows Live Login settings file
        /// in an area on your server that cannot be accessed through
        /// the Internet. This file contains important confidential
        /// information.      
        /// ]]></summary>
        public WindowsLiveLogin(string settingsFile)
        {
            NameValueCollection settings = parseSettings(settingsFile);
 
            string forceDelAuthNonProvisioned = settings["force_delauth_nonprovisioned"];
 
            if (!string.IsNullOrEmpty(forceDelAuthNonProvisioned) && 
                (forceDelAuthNonProvisioned.ToLower() == "true"))
            {
                ForceDelAuthNonProvisioned = true;
            }
            else
            {
                ForceDelAuthNonProvisioned = false;
            }
 
            AppId = settings["appid"];
            Secret = settings["secret"];
            OldSecret = settings["oldsecret"];
            OldSecretExpiry = settings["oldsecretexpiry"];
            SecurityAlgorithm = settings["securityalgorithm"];
            PolicyUrl = settings["policyurl"];
            ReturnUrl = settings["returnurl"];
            BaseUrl = settings["baseurl"];
            SecureUrl = settings["secureurl"];
            ConsentUrl = settings["consenturl"];
        }
 
        string appId;
 
        /// <summary>
        /// Gets or sets the application ID.
        /// </summary>
        public string AppId
        {
            set 
            {
                if (string.IsNullOrEmpty(value)) 
                {
                    if (ForceDelAuthNonProvisioned)
                    {
                        return;
                    }
 
                    throw new ArgumentNullException("value");
                }
 
                Regex re = new Regex(@"^\w+$");
                if (!re.IsMatch(value))
                {
                    throw new ArgumentException("Error: AppId: Application ID must be alphanumeric: " + value);
                }
 
                appId = value; 
            }
 
            get 
            { 
                if (string.IsNullOrEmpty(appId)) 
                {
                    throw new InvalidOperationException("Error: AppId: Application ID was not set. Aborting.");
                }
 
                return appId; 
            }
        }
 
        byte[] cryptKey;
        byte[] signKey;
 
        /// <summary>
        /// Sets your secret key. Use this method if you did not specify 
        /// a secret key at initialization.
        /// </summary>
        public string Secret
        {
            set 
            {
                if (string.IsNullOrEmpty(value)) 
                {
                    if (ForceDelAuthNonProvisioned)
                    {
                        return;
                    }
 
                    throw new ArgumentNullException("value");
                }
 
                if (value.Length < 16)
                {
                    throw new ArgumentException("Error: Secret: Secret key is expected to be longer than 16 characters: " + value.Length);
                }
 
                cryptKey = derive(value, "ENCRYPTION");
                signKey = derive(value, "SIGNATURE");
            }
 
            get { return null; }
        }
 
        byte[] oldCryptKey;
        byte[] oldSignKey;
 
        /// <summary>
        /// Sets your old secret key.
        /// 
        /// Use this property to set your old secret key if you are in the
        /// process of transitioning to a new secret key. You may need this 
        /// property because the Windows Live ID servers can take up to 
        /// 24 hours to propagate a new secret key after you have updated 
        /// your application settings.
        /// 
        /// If an old secret key is specified here and has not expired
        /// (as determined by the OldSecretExpiry setting), it will be used
        /// as a fallback if token decryption fails with the new secret 
        /// key.
        /// </summary>
        public string OldSecret
        {
            set 
            {
                if (string.IsNullOrEmpty(value)) 
                {
                    return;
                }
 
                if (value.Length < 16)
                {
                    throw new ArgumentException("Error: OldSecret: Secret key is expected to be longer than 16 characters: " + value.Length);
                }
 
                oldCryptKey = derive(value, "ENCRYPTION");
                oldSignKey = derive(value, "SIGNATURE");
            }
 
            get { return null; }
        }
 
        string oldSecretExpiryString;
        DateTime oldSecretExpiry;
 
        /// <summary>
        /// Sets or gets the expiry time for your old secret key.
        /// 
        /// After this time has passed, the old secret key will no longer be
        /// used even if token decryption fails with the new secret key.
        ///
        /// The old secret expiry time is represented as the number of seconds
        /// elapsed since January 1, 1970. 
        /// </summary>
        public string OldSecretExpiry
        {
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    return;
                }
 
                oldSecretExpiryString = value;
                int timestampInt;
 
                try 
                {
                    timestampInt = Convert.ToInt32(value);
                } 
                catch (Exception) 
                {
                    throw new ArgumentException("Error: OldSecretExpiry: Invalid timestamp: " 
                                                + value);
                }
 
                DateTime refTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
                oldSecretExpiry = refTime.AddSeconds(timestampInt);
            }
 
            get { return oldSecretExpiryString; }
        }
 
        string securityAlgorithm;
 
        /// <summary>
        /// Sets or gets the version of the security algorithm being used.
        /// </summary>
        public string SecurityAlgorithm
        {
            set { securityAlgorithm = value; }
 
            get 
            { 
                if (string.IsNullOrEmpty(securityAlgorithm))
                {
                    return "wsignin1.0";
                }
 
                return securityAlgorithm; 
            }
        }
 
        bool forceDelAuthNonProvisioned = false;
 
        /// <summary>
        /// Sets or gets a flag that indicates whether Delegated Authentication
        /// is non-provisioned (i.e. does not use an application ID or secret
        /// key).
        /// </summary>
        public bool ForceDelAuthNonProvisioned
        {
            set { forceDelAuthNonProvisioned = value; }
 
            get { return forceDelAuthNonProvisioned; }
        }
 
        string policyUrl;
 
        /// <summary>
        /// Sets or gets the privacy policy URL.
        /// 
        /// Set the property for Delegated Authentication, if you did 
        /// not provide one at initialization time.
        /// </summary>
        public string PolicyUrl
        {
            set 
            { 
                if (string.IsNullOrEmpty(value) && ForceDelAuthNonProvisioned)
                {
                    throw new ArgumentNullException("value");
                }
 
                policyUrl = value; 
            }
 
            get 
            { 
                if (string.IsNullOrEmpty(policyUrl))
                {
                    debug("Warning: In the initial release of Delegated Auth, a Policy URL must be configured in the SDK for both provisioned and non-provisioned scenarios.");
 
                    if (ForceDelAuthNonProvisioned)
                    {
                        throw new InvalidOperationException("Error: PolicyUrl: Policy URL must be set in a Delegated Auth non-provisioned scenario. Aborting.");
                    }
                }
 
                return policyUrl; 
            }
        }
 
        string returnUrl;
 
        /// <summary>
        /// Sets or gets the return URL--the URL on your site to which the consent 
        /// service redirects users (along with the action, consent token, 
        /// and application context) after they have successfully provided 
        /// consent information for Delegated Authentication. 
        /// 
        /// This value will override the return URL specified during 
        /// registration.
        /// </summary>
        public string ReturnUrl
        {
            set 
            { 
                if (string.IsNullOrEmpty(value) && ForceDelAuthNonProvisioned)
                {
                    throw new ArgumentNullException("value");
                }
 
                returnUrl = value; 
            }
 
            get 
            { 
                if (string.IsNullOrEmpty(returnUrl) && ForceDelAuthNonProvisioned)
                {
                    throw new InvalidOperationException("Error: ReturnUrl: Return URL must be specified in a delegated auth non-provisioned scenario. Aborting.");
                }
 
                return returnUrl; 
            }
        }
 
        string baseUrl;
 
        /// <summary>
        /// Sets or gets the URL to use for the Windows Live Login server. 
        /// You should not have to use or change this. Furthermore, we
        /// recommend that you use the Sign In control instead of
        /// the URL methods provided here.
        /// </summary>
        public string BaseUrl
        {
            set { baseUrl = value; }
 
            get 
            { 
                if (string.IsNullOrEmpty(baseUrl))
                {
                    return "http://login.live.com/";
                }
 
                return baseUrl; 
            }
        }
 
        string secureUrl;
 
        /// <summary>
        /// Sets or gets the secure (HTTPS) URL to use for the Windows Live
        /// Login server.  You should not have to use or change this
        /// directly.  
        // </summary>
        public string SecureUrl
        {
            set { secureUrl = value; }
 
            get 
            { 
                if (string.IsNullOrEmpty(secureUrl))
                {
                    return "https://login.live.com/";
                }
 
                return secureUrl; 
            }
        }
 
        string consentUrl;
 
        /// <summary>
        /// Sets or gets the URL to use for the Windows Live Consent server. You
        /// should not have to use or change this directly.
        /// </summary>
        public string ConsentUrl
        {
            set { consentUrl = value; }
 
            get 
            { 
                if (string.IsNullOrEmpty(consentUrl))
                {
                    return "https://consent.live.com/";
                }
 
                return consentUrl;
            }
        }
 
        /* Methods for Web Authentication support. */
 
        /// <summary>
        /// Returns the sign-in URL to use for the Windows Live Login server.
        /// We recommend that you use the Sign In control instead.
        /// </summary>
        /// <returns>Sign-in URL</returns>
        public string GetLoginUrl()
        {
            return GetLoginUrl(null);
        }
 
        /// <summary>
        /// Returns the sign-in URL to use for the Windows Live Login server.
        /// We recommend that you use the Sign In control instead.
        /// </summary>
        /// <param name="context">If you specify it, <paramref
        /// name="context"/> will be returned as-is in the sign-in
        /// response for site-specific use.</param>
        /// <returns>Sign-in URL</returns>
        public string GetLoginUrl(string context)
        {
            return GetLoginUrl(context, null);
        }
 
        /// <summary>
        /// Returns the sign-in URL to use for the Windows Live Login server.
        /// We recommend that you use the Sign In control instead.
        /// </summary>
        /// <param name="context">If you specify it, <paramref
        /// name="context"/> will be returned as-is in the sign-in
        /// response for site-specific use.</param>
        /// <param name="market">The language in which the sign-in page is 
        /// displayed is configured by culture ID (For example, 'fr-fr' or 
        /// 'en-us') specified in the 'market' parameter.</param>
        /// <returns>Sign-in URL</returns>
        public string GetLoginUrl(string context, string market)
        {
            string alg = "&alg=" + SecurityAlgorithm;
 
            context = string.IsNullOrEmpty(context) ? 
              string.Empty : "&appctx=" + HttpUtility.UrlEncode(context);
 
            market = string.IsNullOrEmpty(market) ? 
              string.Empty : "&mkt=" + HttpUtility.UrlEncode(market);
 
            return BaseUrl + "wlogin.srf?appid=" + AppId + 
              alg + context + market;
        }
 
        /// <summary>
        /// Returns the sign-out URL to use for the Windows Live Login server.
        /// We recommend that you use the Sign In control instead.
        /// </summary>
        /// <returns>Sign-out URL</returns>
        public string GetLogoutUrl()
        {
            return GetLogoutUrl(null);
        }
 
        /// <summary>
        /// Returns the sign-out URL to use for the Windows Live Login server.
        /// We recommend that you use the Sign In control instead.
        /// </summary>
        /// <param name="market">The language in which the sign-in page is 
        /// displayed is configured by culture ID (For example, 'fr-fr' or 
        /// 'en-us') specified in the 'market' parameter.</param>
        /// <returns>Sign-out URL</returns>
        public string GetLogoutUrl(string market)
        {
            market = string.IsNullOrEmpty(market) ? 
              string.Empty : "&mkt=" + HttpUtility.UrlEncode(market);
 
            return BaseUrl + "logout.srf?appid=" + AppId + market;
        }
 
        /// <summary>
        /// Holds the user information after a successful sign-in.
        /// </summary>
        public class User
        {
            public User(string timestamp, string id, string flags, string context, string token)
            {
                setTimestamp(timestamp);
                setId(id);
                setFlags(flags);
                setContext(context);
                setToken(token);
            }
 
            DateTime timestamp;
 
            /// <summary>
            ///  Returns the timestamp as obtained from the SSO token.
            /// </summary>
            public DateTime Timestamp { get { return timestamp; } }
 
            /// <summary>
            /// Sets the Unix timestamp.
            /// </summary>
            /// <param name="timestamp"></param>
            private void setTimestamp(string timestamp)
            {
                if (string.IsNullOrEmpty(timestamp))
                {
                    throw new ArgumentException("Error: User: Null timestamp in token.");
                }
 
                int timestampInt;
 
                try 
                {
                    timestampInt = Convert.ToInt32(timestamp);
                } 
                catch (Exception) 
                {
                    throw new ArgumentException("Error: User: Invalid timestamp: " 
                                                + timestamp);
                }
 
                DateTime refTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
                this.timestamp = refTime.AddSeconds(timestampInt);
            }
 
            string id;
 
            /// <summary>
            /// Returns the pairwise unique ID for the user.
            /// </summary>
            public string Id { get { return id; } }
 
            /// <summary>
            /// Sets the pairwise unique ID for the user.
            /// </summary>
            /// <param name="id">User id</param>
            private void setId(string id)
            {
                if (string.IsNullOrEmpty(id))
                {
                    throw new ArgumentException("Error: User: Null id in token.");
                }
 
                Regex re = new Regex(@"^\w+$");
                if (!re.IsMatch(id))
                {
                    throw new ArgumentException("Error: User: Invalid id: " + id);
                }
 
                this.id = id;
            }
 
            bool usePersistentCookie;
 
            /// <summary>
            /// Indicates whether the application
            /// is expected to store the user token in a session or
            /// persistent cookie.
            /// </summary>
            public bool UsePersistentCookie { get { return usePersistentCookie; } }
 
            /// <summary>
            /// Sets the usePersistentCookie flag for the user.
            /// </summary>
            /// <param name="flags"></param>
            private void setFlags(string flags)
            {
                this.usePersistentCookie = false;
 
                if (!string.IsNullOrEmpty(flags))
                {
                    try 
                    {
                        int flagsInt = Convert.ToInt32(flags);
                        this.usePersistentCookie = ((flagsInt % 2) == 1);
                    }
                    catch (Exception) 
                    { 
                        throw new ArgumentException("Error: User: Invalid flags: " 
                                                    + flags);
                    }
                }
            }
 
            string context;
 
            /// <summary>
            /// Returns the application context that was originally passed
            /// to the sign-in request, if any.
            /// </summary>
            public string Context { get { return context; } }
 
            /// <summary>
            /// Sets the the Application context.
            /// </summary>
            /// <param name="context"></param>
            private void setContext(string context)
            {
                this.context = context;
            }
 
            string token;
 
            /// <summary>
            /// Returns the encrypted Web Authentication token containing 
            /// the UID. This can be cached in a cookie and the UID can be
            /// retrieved by calling the ProcessToken method.
            /// </summary>
            public string Token { get { return token; } }
 
            /// <summary>
            /// Sets the the User token.
            /// </summary>
            /// <param name="token"></param>
            private void setToken(string token)
            {
                this.token = token;
            }
        }
 
        /// <summary>
        /// Processes the sign-in response from the Windows Live Login server.
        /// </summary>
        ///
        /// <param name="query">Contains the preprocessed POST query
        /// such as that returned by HttpRequest.Form</param>
        /// 
        /// <returns>The method returns a User object on successful
        /// sign-in; otherwise null.</returns>
        public User ProcessLogin(NameValueCollection query)
        {
            if (query == null)
            {
                debug("Error: ProcessLogin: Invalid query.");
                return null;
            }
 
            string action = query["action"];
 
            if (action != "login")
            {
                debug("Warning: ProcessLogin: query action ignored: " + action);
                return null;
            }
 
            string token = query["stoken"];
            string context = query["appctx"];
 
            if (context != null)
            {
                context = HttpUtility.UrlDecode(context);
            }
 
            return ProcessToken(token, context);
        }
 
        /// <summary>
        /// Decodes and validates a Web Authentication token. Returns a User
        /// object on success.
        /// </summary>
        public User ProcessToken(string token)
        {
            return ProcessToken(token, null);
        }
 
        /// <summary>
        /// Decodes and validates a Web Authentication token. Returns a User
        /// object on success. If a context is passed in, it will be
        /// returned as the context field in the User object.
        /// </summary>
        /// <param name="token">Web Authentication token</param>
        /// <param name="context">If you specify it, <paramref
        /// name="context"/> will be returned as-is in the sign-in
        /// response for site-specific use.</param>        
        /// <returns>User object</returns>
        public User ProcessToken(string token, string context)
        {
            if (string.IsNullOrEmpty(token))
            {
                debug("Error: ProcessToken: Invalid token.");
                return null;
            }
 
            string stoken = DecodeAndValidateToken(token);
 
            if (string.IsNullOrEmpty(stoken))
            {
                debug("Error: ProcessToken: Failed to decode/validate token: " +
                      token);
                return null;
            }
 
            NameValueCollection parsedToken = parse(stoken);
            if (parsedToken == null || parsedToken.Count < 3)
            {
                debug("Error: ProcessToken: Failed to parse token after decoding: " + 
                      token);
                return null;
            }
 
            string appId = parsedToken["appid"];
            if (appId != AppId)
            {
                debug("Error: ProcessToken: Application ID in token did not match ours: " + 
                      appId +  ", " + AppId);
                return null;
            }
 
            User user = null;
            try 
            {
                user = new User(parsedToken["ts"], 
                                parsedToken["uid"], 
                                parsedToken["flags"],
                                context, token);
            } 
            catch (Exception e)
            {
                debug("Error: ProcessToken: Contents of token considered invalid: " + e);
            }
            return user;
        }
 
        /// <summary>
        /// Returns an appropriate content type and body
        /// response that the application handler can return to
        /// signify a successful sign-out from the application.
        /// 
        /// When a user signs out of Windows Live or a Windows Live
        /// application, a best-effort attempt is made to sign the user out
        /// from all other Windows Live applications the user might be signed
        /// in to. This is done by calling the handler page for each
        /// application with 'action' parameter set to 'clearcookie' in the query
        /// string. The application handler is then responsible for clearing
        /// any cookies or data associated with the sign-in. After successfully
        /// signing the user out, the handler should return a GIF (any
        /// GIF) as response to the action=clearcookie query.
        /// </summary>
        public void GetClearCookieResponse(out string type, out byte[] content)
        {
            const string gif = 
              "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7";
            type = "image/gif";
            content = Convert.FromBase64String(gif);
        }
 
        /* Methods for Delegated Authentication support. */
 
        /// <summary>
        /// Returns the consent URL to use for Delegated Authentication for
        /// the given comma-delimited list of offers.
        /// </summary>
        /// <param name="offers">Comma-delimited list of offers.</param>
        /// <returns>Consent URL</returns>
        public string GetConsentUrl(string offers)
        {
            return GetConsentUrl(offers, null);
        }
 
        /// <summary>
        /// Returns the consent URL to use for Delegated Authentication for
        /// the given comma-delimited list of offers.
        /// </summary>
        /// <param name="offers">Comma-delimited list of offers.</param>
        /// <param name="context">If you specify it, <paramref
        /// name="context"/> will be returned as-is in the consent 
        /// response for site-specific use.</param>
        /// <returns>Consent URL</returns>
        public string GetConsentUrl(string offers, string context)
        {
            return GetConsentUrl(offers, context, null);
        }
 
        /// <summary>
        /// Returns the consent URL to use for Delegated Authentication for
        /// the given comma-delimited list of offers.
        /// </summary>
        /// <param name="offers">Comma-delimited list of offers.</param>
        /// <param name="context">If you specify it, <paramref
        /// name="context"/> will be returned as-is in the consent 
        /// response for site-specific use.</param>
        /// <param name="ru">The registered/configured return URL will be 
        /// overridden by 'ru' specified here.</param>
        /// <returns>Consent URL</returns>
        public string GetConsentUrl(string offers, string context, string ru)
        {
            return GetConsentUrl(offers,  context, ru, null);
        }
 
        /// <summary>
        /// Returns the consent URL to use for Delegated Authentication for
        /// the given comma-delimited list of offers.
        /// </summary>
        /// <param name="offers">Comma-delimited list of offers.</param>
        /// <param name="context">If you specify it, <paramref
        /// name="context"/> will be returned as-is in the sign-in
        /// response for site-specific use.</param>
        /// <param name="ru">The registered/configured return URL will be 
        /// overridden by 'ru' specified here.</param>
        /// <param name="market">The language in which the consent page is 
        /// displayed is configured by culture ID (For example, 'fr-fr' or 
        /// 'en-us') specified in the 'market' parameter.</param>
        /// <returns>Consent URL</returns>
        public string GetConsentUrl(string offers, string context, string ru, string market)
        {
            if (string.IsNullOrEmpty(offers))
            {
                throw new ArgumentException("Error: GetConsentUrl: Invalid offers list.");
            }
 
            offers = "?ps=" + HttpUtility.UrlEncode(offers);
 
            context = string.IsNullOrEmpty(context) ? 
              string.Empty : "&appctx=" + HttpUtility.UrlEncode(context);
 
            if (string.IsNullOrEmpty(ru))
            {
                ru = ReturnUrl;
            }
 
            ru = string.IsNullOrEmpty(ru) ? 
              string.Empty : "&ru=" + HttpUtility.UrlEncode(ru);
 
            market = string.IsNullOrEmpty(market) ? 
              string.Empty : "&mkt=" + HttpUtility.UrlEncode(market);
 
            string pu = string.Empty;
 
            if (!string.IsNullOrEmpty(PolicyUrl))
            {
                pu = "&pl=" + HttpUtility.UrlEncode(PolicyUrl);
            }
 
            string app = string.Empty;
 
            if (!ForceDelAuthNonProvisioned)
            {
                app = "&app=" + GetAppVerifier();
            }
 
            return (ConsentUrl + "Delegation.aspx" + offers + context + ru + pu + market + app);
        }
 
        /// <summary>
        /// Returns the URL to use to download a new consent token, given the 
        /// offers and refresh token.
        /// </summary>
        /// <param name="offers">Comma-delimited list of offers.</param>
        /// <param name="refreshToken">Refresh token.</param>
        /// <returns>Refresh consent token URL</returns>
        public string GetRefreshConsentTokenUrl(string offers, string refreshToken)
        {
            return GetRefreshConsentTokenUrl(offers, refreshToken, null);
        }
 
        /// <summary>
        /// Returns the URL to use to download a new consent token, given the 
        /// offers and refresh token.
        /// </summary>
        /// <param name="offers">Comma-delimited list of offers.</param>
        /// <param name="refreshToken">Refresh token.</param>
        /// <returns>Refresh consent token URL</returns>
        /// <param name="ru">The registered/configured return URL will be 
        /// overridden by 'ru' specified here.</param>
        /// <returns>Refresh consent token URL</returns>
        public string GetRefreshConsentTokenUrl(string offers, string refreshToken, string ru)
        {
            if (string.IsNullOrEmpty(offers))
            {
                throw new ArgumentException("Error: GetRefreshConsentTokenUrl: Invalid offers list.");
            }
 
            offers = "?ps=" + HttpUtility.UrlEncode(offers);
 
            if (string.IsNullOrEmpty(refreshToken))
            {
                throw new ArgumentException("Error: GetRefreshConsentTokenUrl: Invalid refresh token.");
            }
 
            refreshToken = "&reft=" + refreshToken;
 
            if (string.IsNullOrEmpty(ru))
            {
                ru = ReturnUrl;
            }
 
            ru = string.IsNullOrEmpty(ru) ? 
              string.Empty : "&ru=" + HttpUtility.UrlEncode(ru);
 
            string app = string.Empty;
 
            if (!ForceDelAuthNonProvisioned)
            {
                app = "&app=" + GetAppVerifier();
            }
 
            return ConsentUrl + "RefreshToken.aspx" + offers + refreshToken + ru + app;
        }
 
        /// <summary>
        /// Returns the URL for the consent-management user interface.
        /// </summary>
        /// <returns>Manage consent URL</returns>
        public string GetManageConsentUrl()
        {
            return GetManageConsentUrl(null);
        }
 
        /// <summary>
        /// Returns the URL for the consent-management user interface.
        /// </summary>
        /// <param name="market">The language in which the consent page is 
        /// displayed is configured by culture ID (For example, 'fr-fr' or 
        /// 'en-us') specified in the 'market' parameter.</param>
        /// <returns>Manage consent URL</returns>
        public string GetManageConsentUrl(string market)
        {
            market = string.IsNullOrEmpty(market) ? 
              string.Empty : "?mkt=" + HttpUtility.UrlEncode(market);
 
            return ConsentUrl + "ManageConsent.aspx" + market;
        }
 
        /// <summary>
        /// Holds the Consent Token object corresponding to consent granted. 
        /// </summary>
        public class ConsentToken
        {
            WindowsLiveLogin wll;
 
            /// <summary>
            /// Initialize the ConsentToken.
            /// </summary>
            /// <param name="wll">WindowsLiveLogin</param>
            /// <param name="delegationToken">Delegation token</param>
            /// <param name="refreshToken">Refresh token</param>
            /// <param name="sessionKey">Session key</param>
            /// <param name="expiry">Expiry</param>
            /// <param name="offers">Offers</param>
            /// <param name="locationID">Location ID</param>
            /// <param name="context">Application context</param>
            /// <param name="decodedToken">Decoded token</param>
            /// <param name="token">Raw token</param>
            public ConsentToken(WindowsLiveLogin wll, string delegationToken, string refreshToken, string sessionKey, string expiry, string offers, string locationID, string context, string decodedToken, string token)
            {
                this.wll = wll;
                setDelegationToken(delegationToken);
                setRefreshToken(refreshToken);
                setSessionKey(sessionKey);
                setExpiry(expiry);
                setOffers(offers);
                setLocationID(locationID);
                setContext(context);
                setDecodedToken(decodedToken);
                setToken(token);
            }
 
            string delegationToken;
 
            /// <summary>
            /// Gets the Delegation token.
            /// </summary>
            public string DelegationToken { get { return delegationToken; } }
 
            /// <summary>
            /// Sets the Delegation token.
            /// </summary>
            /// <param name="delegationToken">Delegation token</param>
            private void setDelegationToken(string delegationToken)
            {
                if (string.IsNullOrEmpty(delegationToken))
                {
                    throw new ArgumentException("Error: ConsentToken: Null delegation token.");
                }
 
                this.delegationToken = delegationToken;
            }
 
            string refreshToken;
 
            /// <summary>
            /// Gets the refresh token.
            /// </summary>
            public string RefreshToken { get { return refreshToken; } }
 
            /// <summary>
            /// Sets the refresh token.
            /// </summary>
            /// <param name="refreshToken">Refresh token</param>
            private void setRefreshToken(string refreshToken)
            {
                this.refreshToken = refreshToken;
            }
 
            byte[] sessionKey;
 
            /// <summary>
            /// Gets the session key.
            /// </summary>
            public byte[] SessionKey { get { return sessionKey; } }
 
            /// <summary>
            /// Sets the session key.
            /// </summary>
            /// <param name="sessionKey">Session key</param>
            private void setSessionKey(string sessionKey)
            {
                if (string.IsNullOrEmpty(sessionKey))
                {
                    throw new ArgumentException("Error: ConsentToken: Null session key.");
                }
 
                this.sessionKey = WindowsLiveLogin.u64(sessionKey);
            }
 
            DateTime expiry;
 
            /// <summary>
            /// Gets the expiry time of delegation token.
            /// </summary>
            public DateTime Expiry { get { return expiry; } }
 
            /// <summary>
            /// Sets the expiry time of delegation token.
            /// </summary>
            /// <param name="expiry">Expiry time</param>
            private void setExpiry(string expiry)
            {
                if (string.IsNullOrEmpty(expiry))
                {
                    throw new ArgumentException("Error: ConsentToken: Null expiry time.");
                }
 
                int expiryInt;
 
                try 
                {
                    expiryInt = Convert.ToInt32(expiry);
                } 
                catch (Exception) 
                {
                    throw new ArgumentException("Error: Consent: Invalid expiry time: " 
                                                + expiry);
                }
 
                DateTime refTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
                this.expiry = refTime.AddSeconds(expiryInt);
            }
 
            IList offers;
 
            /// <summary>
            /// Gets the list of offers/actions for which the user granted consent.
            /// </summary>
            public IList Offers { get { return offers; } }
 
            string offersString;
 
            /// <summary>
            /// Gets the string representation of all the offers/actions for which 
            /// the user granted consent.
            /// </summary>
            public String OffersString { get { return offersString; } }
 
            /// <summary>
            /// Sets the offers/actions for which user granted consent.
            /// </summary>
            /// <param name="offers">Comma-delimited list of offers</param>
            private void setOffers(string offers)
            {
                if (string.IsNullOrEmpty(offers))
                {
                    throw new ArgumentException("Error: ConsentToken: Null offers.");
                }
 
                offers = HttpUtility.UrlDecode(offers);
 
                this.offersString = string.Empty;
                this.offers = new ArrayList();
 
                string[] offersList = offers.Split(new Char[]{';'});
 
                foreach (string offer in offersList)
                {
                    if (!(this.offersString == string.Empty))
                    {
                        this.offersString += ",";
                    }
 
                    int separator = offer.IndexOf(':');
                    if (separator == -1)
                    {
                        debug("Warning: ConsentToken: offer may be invalid: " + offer);
                        this.offers.Add(offer);
                        this.offersString += offer;
                    }
                    else
                    {
                        string o = offer.Substring(0, separator);
                        this.offers.Add(o);
                        this.offersString += o;
                    }
                }
            }
 
            string locationID;
 
            /// <summary>
            /// Gets the location ID.
            /// </summary>
            public string LocationID { get { return locationID; } }
 
            /// <summary>
            /// Sets the location ID.
            /// </summary>
            /// <param name="locationID">Location ID</param>
            private void setLocationID(string locationID)
            {
                this.locationID = locationID;
            }
 
            string context;
 
            /// <summary>
            /// Returns the application context that was originally passed 
            /// to the consent request, if any.
            /// </summary>
            public string Context { get { return context; } }
 
            /// <summary>
            /// Sets the application context.
            /// </summary>
            /// <param name="context">Application context</param>
            private void setContext(string context)
            {
                this.context = context;
            }
 
            string decodedToken;
 
            /// <summary>
            /// Gets the decoded token.
            /// </summary>
            public string DecodedToken { get { return decodedToken; } }
 
            /// <summary>
            /// Sets the decoded token.
            /// </summary>
            /// <param name="decodedToken">Decoded token</param>
            private void setDecodedToken(string decodedToken)
            {
                this.decodedToken = decodedToken;
            }
 
            string token;
 
            /// <summary>
            /// Gets the raw token.
            /// </summary>
            public string Token { get { return token; } }
 
            /// <summary>
            /// Sets the raw token.
            /// </summary>
            /// <param name="token">Raw token</param>
            private void setToken(string token)
            {
                this.token = token;
            }
 
            /// <summary>
            /// Indicates whether the delegation token is set and has not expired.
            /// </summary>
            /// <returns></returns>
            public bool IsValid()
            {
                if (string.IsNullOrEmpty(DelegationToken))
                {
                    return false;
                }
 
                if (DateTime.UtcNow.AddSeconds(-300) > Expiry)
                {
                    return false;
                }
 
                return true;
            }
 
            /// <summary>
            /// Attempt to refresh the current token and replace it. If operation succeeds 
            /// true is returned to signify success.
            /// </summary>
            /// <returns></returns>
            public bool Refresh()
            {
                ConsentToken ct = wll.RefreshConsentToken(this);
 
                if (ct == null) 
                {
                    return false;
                }
 
                copy(ct);
 
                return true;
            }
 
            /// <summary>
            /// Makes a copy of the ConsentToken object.
            /// </summary>
            /// <param name="consentToken"></param>
            void copy(ConsentToken consentToken)
            {
                this.delegationToken = consentToken.delegationToken;
                this.refreshToken = consentToken.refreshToken;
                this.sessionKey = consentToken.sessionKey;
                this.expiry = consentToken.expiry;
                this.offers = consentToken.offers;
                this.locationID = consentToken.locationID;
                this.offersString = consentToken.offersString;
                this.decodedToken = consentToken.decodedToken;
                this.token = consentToken.token;
            }
        }
 
        /// <summary>
        /// Processes the POST response from the Delegated Authentication 
        /// service after a user has granted consent. The processConsent
        /// function extracts the consent token string and returns the result 
        /// of invoking the processConsentToken method. 
        /// </summary>
        /// <param name="query">Response from the Delegated Authentication service.</param>
        /// <returns>ConsentToken</returns>
        public ConsentToken ProcessConsent(NameValueCollection query)
        {
            if (query == null)
            {
                debug("Error: ProcessConsent: Invalid query.");
                return null;
            }
 
            string action = query["action"];
 
            if (action != "delauth")
            {
                debug("Warning: ProcessConsent: query action ignored: " + action);
                return null;
            }
 
            if (query["ResponseCode"] != "RequestApproved")
            {
                debug("Error: ProcessConsent: Consent was not successfully granted: " 
                      + query["ResponseCode"]);
                return null;
            }
 
            string token = query["ConsentToken"];
            string context = query["appctx"];
 
            if (!string.IsNullOrEmpty(context))
            {
                context = HttpUtility.UrlDecode(context);
            }
 
            return ProcessConsentToken(token, context);
        }
 
        /// <summary>
        /// Processes the consent token string that is returned in the POST 
        /// response by the Delegated Authentication service after a 
        /// user has granted consent.
        /// </summary>
        /// <param name="token">Raw token.</param>
        /// <returns>ConsentToken</returns>
        public ConsentToken ProcessConsentToken(string token)
        {
            return ProcessConsentToken(token, null);
        }
 
        /// <summary>
        /// Processes the consent token string that is returned in the POST 
        /// response by the Delegated Authentication service after a 
        /// user has granted consent.
        /// </summary>
        /// <param name="token">Raw token.</param>
        /// <param name="context">If you specify it, <paramref
        /// name="context"/> will be returned as-is in the sign-in
        /// response for site-specific use.</param>
        /// <returns></returns>
        public ConsentToken ProcessConsentToken(string token, string context)
        {
            string decodedToken = token;
 
            if (string.IsNullOrEmpty(token))
            {
                debug("Error: ProcessConsentToken: Null token.");
                return null;
            }
 
            NameValueCollection parsedToken = 
              parse(HttpUtility.UrlDecode(token));
 
            if (!string.IsNullOrEmpty(parsedToken["eact"]))
            {
                decodedToken = DecodeAndValidateToken(parsedToken["eact"]);
                if (string.IsNullOrEmpty(decodedToken))
                {
                    debug("Error: ProcessConsentToken: Failed to decode/validate token: " +
                          token);
                    return null;
                }
 
                parsedToken = parse(decodedToken);
                decodedToken = HttpUtility.UrlEncode(decodedToken);
            }
 
            ConsentToken consentToken = null;
            try
            {
                consentToken = new ConsentToken(this, 
                                                parsedToken["delt"], 
                                                parsedToken["reft"],
                                                parsedToken["skey"],
                                                parsedToken["exp"],
                                                parsedToken["offer"],
                                                parsedToken["lid"],
                                                context, decodedToken,
                                                token);
            }
            catch (Exception e)
            {
                debug("Error: ProcessConsentToken: Contents of token considered invalid: " + e);
            }
 
            return consentToken;
        }
 
        /// <summary>
        /// Attempts to obtain a new, refreshed token and return it. The 
        /// original token is not modified.
        /// </summary>
        /// <param name="token">ConsentToken object.</param>
        /// <returns>Refreshed ConsentToken object.</returns>
        public ConsentToken RefreshConsentToken(ConsentToken token)
        {
            return RefreshConsentToken(token, null);
        }
 
        /// <summary>
        /// Attempts to obtain a new, refreshed token and return it. The 
        /// original token is not modified.
        /// </summary>
        /// <param name="token">ConsentToken object.</param>
        /// <param name="ru">The registered/configured return URL will be 
        /// overridden by 'ru' specified here.</param>
        /// <returns>Refreshed ConsentToken object.</returns>
        public ConsentToken RefreshConsentToken(ConsentToken token, string ru)
        {
            if (token == null)
            {
                debug("Error: RefreshConsentToken: Null consent token.");
                return null;
            }
 
            return RefreshConsentToken(token.OffersString, token.RefreshToken, ru);
        }
 
        /// <summary>
        /// Attempts to obtain a new, refreshed token and return it using 
        /// the offers and refresh token. The original token is not modified.
        /// </summary>
        /// <param name="offers">Comma-delimited list of offers.</param>
        /// <param name="refreshToken">Refresh token.</param>
        /// <returns>Refreshed ConsentToken object.</returns>
        public ConsentToken RefreshConsentToken(string offers, string refreshToken)
        {
            return RefreshConsentToken(offers, refreshToken, null);
        }
 
        /// <summary>
        /// Attempts to obtain a new, refreshed token and return it using 
        /// the offers and refresh token. The original token is not modified.
        /// </summary>
        /// <param name="offers">Comma-delimited list of offers.</param>
        /// <param name="refreshToken">Refresh token.</param>
        /// <param name="ru">The registered/configured return URL will be 
        /// overridden by 'ru' specified here.</param>
        /// <returns>Refreshed ConsentToken object.</returns>
        public ConsentToken RefreshConsentToken(string offers, string refreshToken, string ru)
        {
            string url = null;
 
            try
            {
                url = GetRefreshConsentTokenUrl(offers, refreshToken, ru);
            }
            catch (Exception e)
            {
                debug("Error: Failed to construct refresh consent token URL: " + e);
                return null;
            }
 
            if (string.IsNullOrEmpty(url))
            {
                debug("Error: Failed to construct refresh consent token URL.");
                return null;
            }
 
            string body = fetch(url);
 
            if (string.IsNullOrEmpty(body))
            {
                debug("Error: RefreshConsentToken: Failed to download token.");
                return null;
            }   
 
            Regex re = new Regex("{\"ConsentToken\":\"(.*)\"}");
            GroupCollection gc = re.Match(body).Groups;
 
            if (gc.Count != 2)
            {
                debug("Error: RefreshConsentToken: Failed to extract token: " + body);
                return null;
            }
 
            CaptureCollection cc = gc[1].Captures;
 
            if (cc.Count != 1)
            {
                debug("Error: RefreshConsentToken: Failed to extract token: " + body);
                return null;
            }
 
            return ProcessConsentToken(cc[0].ToString());
        }
 
        /* Common methods. */
 
        /// <summary>
        /// Decodes and validates the raw token.
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public string DecodeAndValidateToken(string token)
        {
            bool haveOldSecret = false;
 
            if ((oldSecretExpiry != null) && (DateTime.UtcNow < oldSecretExpiry)) 
            {
                if ((oldCryptKey != null) && (oldSignKey != null))
                {
                    haveOldSecret = true;
                }
            }
 
            string stoken = DecodeAndValidateToken(token, cryptKey, signKey);
 
            if (string.IsNullOrEmpty(stoken))
            {
                if (haveOldSecret)
                {
                    debug("Warning: Failed to validate token with current secret, attempting old secret.");
                    return DecodeAndValidateToken(token, oldCryptKey, oldSignKey);
                }
            }
 
            return stoken;
        }
 
        /// <summary>
        /// Decodes and validates the raw token with appropriate crypt key 
        /// and sign key.
        /// </summary>
        /// <param name="token">Raw token.</param>
        /// <param name="cryptKey">Crypt key.</param>
        /// <param name="signKey">Sign key.</param>
        /// <returns></returns>
        public string DecodeAndValidateToken(string token, byte[] cryptKey, byte[] signKey)
        {
            string stoken = DecodeToken(token, cryptKey);
 
            if (!string.IsNullOrEmpty(stoken))
            {
                stoken = ValidateToken(stoken, signKey);
            }
 
            return stoken;
        }
 
        /// <summary>
        /// Decode the given token. Returns null on failure.
        /// </summary>
        ///
        /// <list type="number">
        /// <item>First, the string is URL unescaped and base64
        /// decoded.</item>
        /// <item>Second, the IV is extracted from the first 16 bytes
        /// of the string.</item>
        /// <item>Finally, the string is decrypted by using the
        /// encryption key.</item> 
        /// </list>
        /// <param name="token">Raw token.</param>
        /// <returns>Decoded token.</returns>
        public string DecodeToken(string token)
        {
            return DecodeToken(token, cryptKey);
        }
 
        /// <summary>
        /// Decode the given token. Returns null on failure.
        /// </summary>
        ///
        /// <list type="number">
        /// <item>First, the string is URL unescaped and base64
        /// decoded.</item>
        /// <item>Second, the IV is extracted from the first 16 bytes
        /// of the string.</item>
        /// <item>Finally, the string is decrypted by using the
        /// encryption key.</item> 
        /// </list>
        /// <param name="token">Raw token.</param>
        /// <param name="cryptKey">Crypt key.</param>
        /// <returns>Decoded token.</returns>
        public string DecodeToken(string token, byte[] cryptKey)
        {
            if (cryptKey == null || cryptKey.Length == 0)
            {
                throw new InvalidOperationException("Error: DecodeToken: Secret key was not set. Aborting.");
            }
 
            if (string.IsNullOrEmpty(token))
            {
                debug("Error: DecodeToken: Null token input.");
                return null;
            }
 
            const int ivLength = 16;
            byte[] ivAndEncryptedValue = u64(token);
 
            if ((ivAndEncryptedValue == null) || 
                (ivAndEncryptedValue.Length <= ivLength) || 
                ((ivAndEncryptedValue.Length % ivLength) != 0))
            {
                debug("Error: DecodeToken: Attempted to decode invalid token.");
                return null;
            }
 
            Rijndael aesAlg = null;
            MemoryStream memStream = null;
            CryptoStream cStream = null;
            StreamReader sReader = null;
            string decodedValue = null;
 
            try 
            {
                aesAlg = new RijndaelManaged();
                aesAlg.KeySize = 128;
                aesAlg.Key = cryptKey;
                aesAlg.Padding = PaddingMode.PKCS7;
                memStream = new MemoryStream(ivAndEncryptedValue);
                byte[] iv = new byte[ivLength];
                memStream.Read(iv, 0, ivLength);
                aesAlg.IV = iv;
                cStream = new CryptoStream(memStream, aesAlg.CreateDecryptor(), CryptoStreamMode.Read);
                sReader = new StreamReader(cStream, Encoding.ASCII);
                decodedValue = sReader.ReadToEnd();
            } 
            catch (Exception e) 
            {
                debug("Error: DecodeToken: Decryption failed: " + e);
                return null;
            } 
            finally 
            {
                try 
                {
                    if (sReader != null) { sReader.Close(); }
                    if (cStream != null) { cStream.Close(); }
                    if (memStream != null) { memStream.Close(); }
                    if (aesAlg != null) { aesAlg.Clear(); }
                } 
                catch (Exception e) 
                {
                    debug("Error: DecodeToken: Failure during resource cleanup: " + e);
                }
            }
 
            return decodedValue;
        }
 
        /// <summary>
        /// Creates a signature for the given string.
        /// </summary>
        public byte[] SignToken(string token)
        {
            return SignToken(token, signKey);
        }
 
        /// <summary>
        /// Creates a signature for the given string by using the
        /// signature key.
        /// </summary>
        public byte[] SignToken(string token, byte[] signKey)
        {
            if (signKey == null || signKey.Length == 0)
            {
                throw new InvalidOperationException("Error: SignToken: Secret key was not set. Aborting.");
            }
 
            if (string.IsNullOrEmpty(token))
            {
                debug("Attempted to sign null token.");
                return null;
            }
 
            using (HashAlgorithm hashAlg = new HMACSHA256(signKey)) 
            {
                byte[] data = Encoding.Default.GetBytes(token);
                byte[] hash = hashAlg.ComputeHash(data);
                return hash;
            }
        }
 
        /// <summary>
        /// Extracts the signature from the token and validates it.
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public string ValidateToken(string token)
        {
            return ValidateToken(token, signKey);
        }
 
        /// <summary>
        /// Extracts the signature from the token and validates it by using the 
        /// signature key.
        /// </summary>
        public string ValidateToken(string token, byte[] signKey)
        {
            if (string.IsNullOrEmpty(token))
            {
                debug("Error: ValidateToken: Null token.");
                return null;
            }
 
            string[] s = { "&sig=" };
            string[] bodyAndSig = token.Split(s, StringSplitOptions.None);
 
            if (bodyAndSig.Length != 2)
            {
                debug("Error: ValidateToken: Invalid token: " + token);
                return null;
            }
 
            byte[] sig = u64(bodyAndSig[1]);
 
            if (sig == null)
            {
                debug("Error: ValidateToken: Could not extract the signature from the token.");
                return null;
            }
 
            byte[] sig2 = SignToken(bodyAndSig[0], signKey);
 
            if (sig2 == null)
            {
                debug("Error: ValidateToken: Could not generate a signature for the token.");
                return null;
            }
 
            if (sig.Length == sig2.Length) 
            {
                for (int i = 0; i < sig.Length; i++) 
                {
                    if (sig[i] != sig2[i]) { goto badSig; }
                }
 
                return token;
            }
 
        badSig:
            debug("Error: ValidateToken: Signature did not match.");
            return null;
        }
 
        /* Implementation of the methods needed to perform Windows Live
           application verification as well as trusted sign-in. */
 
        /// <summary>
        /// Generates an Application Verifier token.
        /// </summary>
        public string GetAppVerifier()
        {
            return GetAppVerifier(null);
        }
 
        /// <summary>
        /// Generates an Application Verifier token. An IP address
        /// can be included in the token.
        /// </summary>
        public string GetAppVerifier(string ip)
        {
            ip = string.IsNullOrEmpty(ip) ? string.Empty : ("&ip=" + ip);
            string token = "appid=" + AppId + "&ts=" + getTimestamp() + ip;
            string sig = e64(SignToken(token));
 
            if (string.IsNullOrEmpty(sig))
            {
                debug("Error: GetAppVerifier: Failed to sign the token.");
                return null;
            }
 
            token += "&sig=" + sig;
            return HttpUtility.UrlEncode(token);
        }        
 
        /// <summary>
        /// Returns the URL needed to retrieve the application
        /// security token. The application security token
        /// will be generated for the Windows Live site.
        ///
        /// JavaScript Output Notation (JSON) output is returned:
        ///
        /// {"token":"&lt;value&gt;"}
        /// </summary>
        public string GetAppLoginUrl()
        {
            return GetAppLoginUrl(null, null, false);
        }
 
        /// <summary>
        /// Returns the URL needed to retrieve the application
        /// security token.
        ///
        /// By default, the application security token will be
        /// generated for the Windows Live site; a specific Site ID
        /// can optionally be specified in 'siteId'.
        ///
        /// JSON output is returned:
        ///
        /// {"token":"&lt;value&gt;"}
        /// </summary>
        public string GetAppLoginUrl(string siteId)
        {
            return GetAppLoginUrl(siteId, null, false);
        }
 
        /// <summary>
        /// Returns the URL needed to retrieve the application
        /// security token.
        ///
        /// By default, the application security token will be
        /// generated for the Windows Live site; a specific Site ID
        /// can optionally be specified in 'siteId'. The IP address
        /// can also optionally be included in 'ip'.
        ///
        /// JSON output is returned:
        ///
        /// {"token":"&lt;value&gt;"}
        /// </summary>
        public string GetAppLoginUrl(string siteId, string ip)
        {
            return GetAppLoginUrl(siteId, ip, false);
        }
 
        /// <summary>
        /// Returns the URL needed to retrieve the application
        /// security token.
        ///
        /// By default, the application security token will be
        /// generated for the Windows Live site; a specific Site ID
        /// can optionally be specified in 'siteId'. The IP address
        /// can also optionally be included in 'ip'.
        ///
        /// If 'js' is false, then JSON output is returned: 
        ///
        /// {"token":"&lt;value&gt;"}
        ///
        /// Otherwise, a JavaScript response is returned. It is assumed
        /// that WLIDResultCallback is a custom function implemented to
        /// handle the token value:
        /// 
        /// WLIDResultCallback("&lt;tokenvalue&gt;");
        /// </summary>
        public string GetAppLoginUrl(string siteId, string ip, bool js)
        {
            string algPart = "&alg=" + SecurityAlgorithm;
            string sitePart = string.IsNullOrEmpty(siteId) ? 
              string.Empty : "&id=" + siteId;
            string jsPart = (!js) ? string.Empty : "&js=1";
            string url = SecureUrl + "wapplogin.srf?app=" + 
              GetAppVerifier(ip) + algPart + sitePart + jsPart;
            return url;            
        }
 
        /// <summary>
        /// Retrieves the application security token for application
        /// verification from the application sign-in URL. The
        /// application security token will be generated for the
        /// Windows Live site.
        /// </summary>
        public string GetAppSecurityToken()
        {
            return GetAppSecurityToken(null, null);
        }
 
        /// <summary>
        /// Retrieves the application security token for application
        /// verification from the application sign-in URL.
        ///
        /// By default, the application security token will be
        /// generated for the Windows Live site; a specific Site ID
        /// can optionally be specified in 'siteId'.
        /// </summary>
        public string GetAppSecurityToken(string siteId)
        {
            return GetAppSecurityToken(siteId, null);
        }
 
        /// <summary>
        /// Retrieves the application security token for application
        /// verification from the application sign-in URL.
        ///
        /// By default, the application security token will be
        /// generated for the Windows Live site; a specific Site ID
        /// can optionally be specified in 'siteId'. The IP address
        /// can also optionally be included in 'ip'.
        ///
        /// Implementation note: The application security token is
        /// downloaded from the application sign-in URL in JSON format
        /// {"token":"&lt;value&gt;"}, so we need to extract
        /// &lt;value&gt; from the string and return it as seen here.
        /// </summary>
        public string GetAppSecurityToken(string siteId, string ip)
        {
            string url = GetAppLoginUrl(siteId, ip);
            string body = fetch(url);
            if (string.IsNullOrEmpty(body))
            {
                debug("Error: GetAppSecurityToken: Failed to download token.");
                return null;
            }   
 
            Regex re = new Regex("{\"token\":\"(.*)\"}");
            GroupCollection gc = re.Match(body).Groups;
 
            if (gc.Count != 2)
            {
                debug("Error: GetAppSecurityToken: Failed to extract token: " + body);
                return null;
            }
 
            CaptureCollection cc = gc[1].Captures;
 
            if (cc.Count != 1)
            {
                debug("Error: GetAppSecurityToken: Failed to extract token: " + body);
                return null;
            }
 
            return cc[0].ToString();
        }
 
        /// <summary>
        /// Returns a string that can be passed to the GetTrustedParams
        /// function as the 'retcode' parameter. If this is specified as
        /// the 'retcode', then the app will be used as return URL
        /// after it finishes trusted sign-in.  
        /// </summary>
        public string GetAppRetCode()
        {
            return "appid=" + AppId;
        }
 
        /// <summary>
        /// Returns a table of key-value pairs that must be posted to
        /// the sign-in URL for trusted sign-in. Use HTTP POST to do
        /// this. Be aware that the values in the table are neither
        /// URL nor HTML escaped and may have to be escaped if you are
        /// inserting them in code such as an HTML form.
        /// 
        /// The user to be trusted on the local site is passed in as
        /// string 'user'.
        /// </summary>
        public NameValueCollection GetTrustedParams(string user)
        {
            return GetTrustedParams(user, null);
        }
 
        /// <summary>
        /// Returns a table of key-value pairs that must be posted to
        /// the sign-in URL for trusted sign-in. Use HTTP POST to do
        /// this. Be aware that the values in the table are neither
        /// URL nor HTML escaped and may have to be escaped if you are
        /// inserting them in code such as an HTML form.
        /// 
        /// The user to be trusted on the local site is passed in as
        /// string 'user'.
        /// 
        /// Optionally, 'retcode' specifies the resource to which
        /// successful sign-in is redirected, such as Windows Live Mail,
        /// and is typically a string in the format 'id=2000'. If you
        /// pass in the value from GetAppRetCode instead, sign-in will
        /// be redirected to the application. Otherwise, an HTTP 200
        /// response is returned.
        /// </summary>
        public NameValueCollection GetTrustedParams(string user, string retcode)
        {
            string token = GetTrustedToken(user);
 
            if (string.IsNullOrEmpty(token)) { return null; }
 
            token = "<wst:RequestSecurityTokenResponse xmlns:wst=\"http://schemas.xmlsoap.org/ws/2005/02/trust\"><wst:RequestedSecurityToken><wsse:BinarySecurityToken xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">" + token + "</wsse:BinarySecurityToken></wst:RequestedSecurityToken><wsp:AppliesTo xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\"><wsa:EndpointReference xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\"><wsa:Address>uri:WindowsLiveID</wsa:Address></wsa:EndpointReference></wsp:AppliesTo></wst:RequestSecurityTokenResponse>";
 
            NameValueCollection nvc = new NameValueCollection(3);
            nvc["wa"] = SecurityAlgorithm;
            nvc["wresult"] = token;
 
            if (retcode != null) 
            {
                nvc["wctx"] = retcode;
            }
 
            return nvc;
        }
 
        /// <summary>
        /// Returns the trusted sign-in token in the format needed by the
        /// trusted sign-in gadget.
        ///
        /// User to be trusted on the local site is passed in as string
        /// 'user'.
        /// </summary>
        public string GetTrustedToken(string user)
        {
            if (string.IsNullOrEmpty(user))
            {
                debug("Error: GetTrustedToken: Invalid user specified.");
                return null;
            }
 
            string token = "appid=" + AppId + "&uid=" + 
              HttpUtility.UrlEncode(user) + "&ts=" + getTimestamp();
            string sig = e64(SignToken(token));
 
            if (string.IsNullOrEmpty(sig))
            {
                debug("Error: GetTrustedToken: Failed to sign the token.");
                return null;
            }
 
            token += "&sig=" + sig;
            return HttpUtility.UrlEncode(token);
        }
 
        /// <summary>
        /// Returns the trusted sign-in URL to use for the Windows Live
        /// Login server. 
        /// </summary>
        public string GetTrustedLoginUrl()
        {
            return SecureUrl + "wlogin.srf";
        }
 
        /// <summary>
        /// Returns the trusted sign-out URL to use for the Windows Live
        /// Login server. 
        /// </summary>
        public string GetTrustedLogoutUrl()
        {
            return SecureUrl + "logout.srf?appid=" + AppId;
        }
 
        /* Helper methods */
 
        /// <summary>
        /// Function to parse the settings file.
        /// </summary>
        /// <param name="settingsFile"></param>
        /// <returns></returns>
        static NameValueCollection parseSettings(string settingsFile)
        {
            if (string.IsNullOrEmpty(settingsFile))
            {
                throw new ArgumentNullException("settingsFile");
            }
 
            // Throws an exception on any failure.