In our previous posting, we discussed the basics of setting up delegated authentication using a simple ASP.NET Web site. In this posting we will explore the code in greater detail, and expand the capabilities of the sample to include checking the expiry date of the token, and refreshing the token. This posting shows highlights of the code that is used to perform these tasks, but not the complete code. Refer to the sample code for the full source.

Click here to download the sample code

Note: The instructions for setting up prerequisites have been moved to the sample code project. See readme.htm to access the instructions. Pay particularly close attention if you plan to test the application locally, there are a couple of extra steps you need to take so that cookies will work properly in the local environment.

Creating the Hyperlink

The first order of business is displaying a hyperlink for the Windows Live sign-in page. This hyperlink contains an assortment of information, including:

  • The URL for the consent page.
  • A list of offers. These determine the types of permissions that should be granted.
  • The return URL. This should point to the response handler page (more about that later).
  • The URL for the privacy policy page (in this sample it is merely a placeholder).
  • The appid and appUrl values that were provided when you provisioned the application.

Altogether the URL looks like this, as it is generated by calling the GetConsentUrl method in the WindowsLiveLogin class when the page loads (line breaks added for clarity):

https://developer.mesh-ctp.com/web/apps/appconsent.aspx?
ps=LiveMeshFolder.Read%2cProfiles.Read%2cContacts.Read%2cNews.Read
&ru=http%3a%2f%2flivedocs3.com%2fLiveFX_DelAuth%2fdelauth-handler.aspx
&pl=http%3a%2f%2flivedocs3.com%2fLiveFX_DelAuth%2fPolicy.aspx
&app=appid%3d000000004800582F%26ts%3d1229629618%26sig%3d%
252bZRR4MStePHXezuSb3onuRDCOfvW0M28P7nLYq1re2c%253d
&appUrl=/Live/V0.1/Mesh/Applications/FAN5567B47NEXA5P2GWFSJRRAQ

The following code shows creating an instance of WindowsLiveLogin and calling GetConsentUrl.

// Create a new instance of WindowsLiveLogin and get a URL string.
WindowsLiveLogin wl_login = new WindowsLiveLogin(true);
string consentUrl = wl_login.GetConsentUrl(Offers);

On the default.aspx page, the hyperlink is presented as follows:

<span id="linkConsent" runat="server"><a href="<%=consentUrl%>">Click here</a> to grant consent for this application to access your Windows Live data.</span>

WindowsLiveLogin gets the values to use for the URL from the web.config file for the project. When you first open the web.config, look for the following section.

<!-- IMPORTANT: These values must be entered before running the sample. -->
<!--<appSettings>
    <add key="wll_appid" value="[Your appid.]"/>
    <add key="wll_secret" value="[Your secret key.]"/>
    <add key="wll_securityalgorithm" value="wsignin1.0"/>
    <add key="wll_returnurl" value="[The full URL for your delegated auth handler page.]"/>
    <add key="wll_policyurl" value="[The full URL for your site's privacy policy page.]"/>
    <add key="wll_consenturl" value="https://developer.mesh-ctp.com/web/apps/appconsent.aspx"/>
    <add key="wll_appurl" value="/Live/V0.1/Mesh/Applications/[The id provided by Azure Services Portal when you registered your app.]"/>
</appSettings>

IMPORTANT: The AppID and the final segment of the Application URL (self link) are two discrete values. Take care not to get these values mixed up. See readme.htm in the sample code for more detail about these settings.

Keep in mind that WindowsLiveLogin is also sample code, taken directly from the Windows Live ID Delegated Authentication SDK for Application Providers. Nothing here is set in stone, so feel free to make modifications, and extend the classes as you see fit.

Handling the Return

Once the user has granted consent to access the resources specified in the offers string, a request is sent back to the return URL. Code needs to be put into place to handle this request. This is done by delauth-handler.aspx and delauth-handler.aspx.cs. Since the return URL points directly to this page, the request is intercepted by using a standard HttpRequest object as shown in the following example.

protected void Page_Load(object sender, EventArgs e)
{
    HttpRequest Request = HttpContext.Current.Request;
    HttpResponse Response = HttpContext.Current.Response;
 
    if (Request["action"] == "delauth")
    {
        // Call ProcessConsent to create a new token.
        consentToken = wl_login.ProcessConsent(Request.Form);
     }
    // Redirect to the login page.
    Response.Redirect(LoginPage);
    } 
}

Getting the Token Data

At this point let's say a user has visited the application, clicked the link, and granted consent to allow the application to view their Windows Live data. The response handler page has redirected us back to the login page, where we now have a delegated authentication token that we'll use to make Live Framework requests on the user's behalf. But there is an issue we have not yet addressed, and that is how to get the delegated authentication token across from the handler page to the default page (and to other pages on the site that may need to use the token). The answer to this is cookies. In the previous version of this sample session cookies were used. This version of the sample offers a more robust system for managing token data, so standard cookies are used instead. This necessitates some changes. First, default.aspx.cs needs some code to check for the existence of a cookie, refresh the cookie if it is expired, and extract delegated authentication data from the cookie. In the following code, the UI that is presented on the default page is toggled based on whether the cookie can be located.

protected void Page_Load(object sender, EventArgs e)
{
    // Generate the consent URL by calling GetConsentUrl
    // from the WindowsLiveLogin class.
    consentUrl = wl_login.GetConsentUrl(Offers);
 
    // Toggle the UI elements that are displayed.
    // This depends upon whether a token can be found in the site cookie.
    if (Request.Cookies["consentToken"] == null)
    {
        lblConsent.Text = "";
        linkConsent.Visible = true;
        manageConsent.Visible = false;
        connectPrompt.Visible = false;
    }
    else
    {
        // If there is a cookie, check the expiry value to determine whether refresh is required.
        if (DateTime.UtcNow.AddSeconds(-300) > (DateTime.Parse(Request.Cookies["consentToken"]["expiry"])))
        {
            WindowsLiveLogin.ConsentToken consentToken = authHelpers.getNewTokenwithRefreshToken(Request.Cookies["consentToken"]["offersString"], Request.Cookies["consentToken"]["refreshToken"]);
            authHelpers.CreateTokenCookie(consentToken);
        }
 
        lblConsent.Text = "Consent acquired: " + Request.Cookies["consentToken"]["delToken"];
        linkConsent.Visible = false;
        manageConsent.Visible = true;
        connectPrompt.Visible = true;
    }
}

The following code shows delauth-handler.aspx.cs, now expanded with some basic cookie management code.

protected void Page_Load(object sender, EventArgs e)
{
    // Create a new instance of the AuthenticationHelpers class.
    AuthenticationHelperClass authHelpers = new AuthenticationHelperClass();
 
    // Create a consent token
    WindowsLiveLogin.ConsentToken consentToken = null;
    HttpRequest Request = HttpContext.Current.Request;
    HttpResponse Response = HttpContext.Current.Response;
 
    if (Request["action"] == "delauth")
    {
        // Check if a cookie exists.
        if (Request.Cookies["consentToken"] != null)
        {
            // If the cookie exists, check the expiry and refresh if it is expired.
            if (DateTime.UtcNow.AddSeconds(-300) > (DateTime.Parse(Request.Cookies["consentToken"]["expiry"])))
            {
                consentToken = authHelpers.getNewTokenwithRefreshToken(Request.Cookies["consentToken"]["offersString"], Request.Cookies["consentToken"]["refreshToken"]);
                authHelpers.CreateTokenCookie(consentToken);
            }
        }
 
        else
        {
            // Call ProcessConsent to create a new token.
            consentToken = wl_login.ProcessConsent(Request.Form);
            authHelpers.CreateTokenCookie(consentToken);
        }
 
        // Redirect to the login page.
        Response.Redirect(LoginPage);
    } 
}

This is where the AuthenticationHelperClass class comes into play. When the handler page loads, it now checks for the existence of the consentToken cookie. If such a cookie is found it checks the value of the expiry key. If the token is expired, a call is made to getNewTokenwithRefreshToken. If no token exists, consent is processed, and the resulting token data is added to the site cookie. The cookie creation code in AuthenticationHelperClass looks like this.

// Create a cookie and assign keys to each piece of token data.
public void CreateTokenCookie(WindowsLiveLogin.ConsentToken consentToken)
{
    HttpCookie tokenCookie = new HttpCookie("consentToken");
    HttpResponse Response = HttpContext.Current.Response;
    tokenCookie["delToken"] = consentToken.DelegationToken;
    tokenCookie["refreshToken"] = consentToken.RefreshToken;
    tokenCookie["sessionKey"] = consentToken.SessionKey.ToString();
    tokenCookie["expiry"] = consentToken.Expiry.ToString();
    tokenCookie["offersString"] = consentToken.OffersString;
    tokenCookie["locationId"] = consentToken.LocationID;
    tokenCookie["context"] = consentToken.Context;
    tokenCookie["decodedtoken"] = consentToken.DecodedToken;
    tokenCookie["token"] = consentToken.Token;
    tokenCookie.Expires = DateTime.Now.AddDays(30d);
    Response.Cookies.Add(tokenCookie);
}

This code creates a new cookie named consentToken, then adds each piece of token data to the cookie as a key, and assigns an expiry date of 30 days from the current day. Although some of the data is not used in the sample, it is a good idea to save all of it for maximum flexibility in the future. For example, you could use the cookie data to completely re-create a new ConsentToken with updated offers. Back to the cookie expiry date, this example sets it for 30 days for the sake of simplicity. You can incorporate a more sophisticated scheme for managing the expiry dates of the token, and align the cookie expiry date accordingly. For example, you could set it up so that the cookie and the token expire at the same time. Since the ConsentToken.Expiry property is a DateTime value, you could set the cookie expiry date based on that. For example:

tokenCookie.Expires = consentToken.Expiry.AddDays(30d);

The refresh code uses an HttpWebRequest object and System.Net.WebRequest to generate a refresh token URL, StreamReader to read the response, and JavaScriptSerializer to deserialize the response. The following code snippet shows the code that is used to do this.

//Get Renewed ConsentToken Object with RefreshToken
public WindowsLiveLogin.ConsentToken getNewTokenwithRefreshToken(string offerString, string refreshToken)
{
    //WindowsLiveLogin wll = new WindowsLiveLogin(true);
    string refreshTokenURL = wl_login.GetRefreshConsentTokenUrl(offerString, refreshToken);
 
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(refreshTokenURL);
    request.Method = "GET";
    request.ContentType = "application/json";
 
    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
 
    StreamReader streamReader = new StreamReader(response.GetResponseStream());
    string jsonResult = streamReader.ReadToEnd();
 
    JavaScriptSerializer jsonSerializer = new JavaScriptSerializer();
 
    string renewedRawTokenString = jsonSerializer.Deserialize<refreshTokenObj>(jsonResult).ConsentToken;
    return wl_login.ProcessConsentToken(renewedRawTokenString);
}
 
//ConsentToken Info Object to be Deserialized
class refreshTokenObj
{
    public string ConsentToken { get; set; }
}

Doing Stuff with the Live Framework

Now that consent has been granted, and a cookie has been saved to store all of the delegated authentication token data, we can begin working with the Live Framework. This sample illustrates getting the names of each folder, and displaying them. The sample has two methods, one to connect to the Live Operating Environment (LOE), and one to page through and display the names of associated Mesh objects. The code for creating an instance of the LOE looks like this:

// Create a new instance of the Live Operating Environment (loe).
LiveOperatingEnvironment loe = new LiveOperatingEnvironment();

The code for connecting to the LOE is as follows.

protected void  ConnectToLOE(object sender, EventArgs e)
{
    // Connect to the Live Operating Environment.
    loe.Connect(Request.Cookies["consentToken"]["delToken"], AuthenticationTokenType.DelegatedAuthToken, LiveFxUri, accessOptions);
 
    // Create a new Mesh object from the loe.
    // This is the object your program will use for the majority of interactions.
    Mesh mesh = loe.Mesh;
 
    // Show some text to indicate that the user is connected.
    userName.InnerText = mesh.ProvisionedUser.Name + " is now connected.";
 
    // Call the method to display a list of Mesh objects.
    DisplayMeshItems(mesh);
}

The ConnectToLOE method calls the LiveOperatingEnvironment.Connect method using the delegation token key that was saved with the current cookie, indicating an AuthenticationTokenType of DelegatedAuthToken, and passing the accessOptions that have been defined (not shown in this example). Next, a Mesh object is obtained from the LOE instance. This is used to display the user's name in a confirmation message, and to call the DisplayMeshItems method (shown in the following snippet).

private void DisplayMeshItems(Mesh mesh)
{
    // Display all Mesh Objects (in this case, all Mesh Folders).
    StringBuilder meshObjectList = new StringBuilder("");
    mesh.MeshObjects.Load();
 
    var meshObjects = (from meshObject in mesh.CreateQuery<MeshObject>() select meshObject);
 
    foreach (MeshObject meshObject in meshObjects)
    {
        meshObjectList.Append(meshObject.Resource.Title + ", ");
    }
    connected.InnerText = "Mesh Folders for " + mesh.ProvisionedUser.Name + ": " + meshObjectList;
}

At this point, depending on the offers and corresponding consent granted by the user, the application can be empowered to do everything from read the user's data, to creating, updating, and deleting items in the user's Mesh.

--Live Framework team