Friday, July 13, 2012

Metro, Authentication, and the ASP.NET Web API

Metro, Authentication, and the ASP.NET Web API:
Imagine that you want to create a Metro style app written with JavaScript and you want to communicate with a remote web service. For example, you are creating a movie app which retrieves a list of movies from a movies service. In this situation, how do you authenticate your Metro app and the Metro user so not just anyone can call the movies service? How can you identify the user making the request so you can return user specific data from the service?

The Windows Live SDK supports a feature named Single Sign-On. When a user logs into a Windows 8 machine using their Live ID, you can authenticate the user’s identity automatically. Even better, when the Metro app performs a call to a remote web service, you can pass an authentication token to the remote service and prevent unauthorized access to the service.

The documentation for Single Sign-On is located here:

http://msdn.microsoft.com/en-us/library/live/hh826544.aspx

In this blog entry, I describe the steps that you need to follow to use Single Sign-On with a (very) simple movie app. We build a Metro app which communicates with a web service created using the ASP.NET Web API.


Creating the Visual Studio Solution

Let’s start by creating a Visual Studio solution which contains two projects: a Windows Metro style Blank App project and an ASP.NET MVC 4 Web Application project. Name the Metro app MovieApp and the ASP.NET MVC application MovieApp.Services. When you create the ASP.NET MVC application, select the Web API template:

clip_image002

After you create the two projects, your Visual Studio Solution Explorer window should look like this:

clip_image003


Configuring the Live SDK

You need to get your hands on the Live SDK and register your Metro app. You can download the latest version of the SDK (version 5.2) from the following address:

http://www.microsoft.com/en-us/download/details.aspx?id=29938

After you download the Live SDK, you need to visit the following website to register your Metro app:

https://manage.dev.live.com/build

Don’t let the title of the website — Windows Push Notifications & Live Connect – confuse you, this is the right place. Follow the instructions at the website to register your Metro app. Don’t forget to follow the instructions in Step 3 for updating the information in your Metro app’s manifest.

After you register, your client secret is displayed. Record this client secret because you will need it later (we use it with the web service):

clip_image004

You need to configure one more thing. You must enter your Redirect Domain by visiting the following website:

https://manage.dev.live.com/Applications/Index

Click on your application name, click Edit Settings, click the API Settings tab, and enter a value for the Redirect Domain field. You can enter any domain that you please just as long as the domain has not already been taken:

clip_image005

For the Redirect Domain, I entered http://superexpertmovieapp.com.


Create the Metro MovieApp

Next, we need to create the MovieApp. The MovieApp will:

1. Use Single Sign-On to log the current user into Live

2. Call the MoviesService web service

3. Display the results in a ListView control

Because we use the Live SDK in the MovieApp, we need to add a reference to it. Right-click your References folder in the Solution Explorer window and add the reference:

clip_image007

Here’s the HTML page for the Metro App:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>MovieApp</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.1.0.RC/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.1.0.RC/js/base.js"></script>
    <script src="//Microsoft.WinJS.1.0.RC/js/ui.js"></script>

    <!-- Live SDK -->
    <script type="text/javascript" src="/LiveSDKHTML/js/wl.js"></script>

    <!-- WebServices references -->
    <link href="/css/default.css" rel="stylesheet" />
    <script src="/js/default.js"></script>
</head>
<body>
    <div id="tmplMovie"
        data-win-control="WinJS.Binding.Template">
        <div class="movieItem">
            <span data-win-bind="innerText:title"></span>
            <br /><span data-win-bind="innerText:director"></span>
        </div>
    </div>

    <div id="lvMovies"
        data-win-control="WinJS.UI.ListView"
        data-win-options="{
            itemTemplate: select('#tmplMovie')
        }">
    </div>

</body>
</html>
The HTML page above contains a Template and ListView control. These controls are used to display the movies when the movies are returned from the movies service.
Notice that the page includes a reference to the Live script that we registered earlier:

<!-- Live SDK --> 

<script type="text/javascript" src="/LiveSDKHTML/js/wl.js"></script> 
The JavaScript code looks like this:

(function () {
    "use strict";

    var REDIRECT_DOMAIN = "http://superexpertmovieapp.com";
    var WEBSERVICE_URL = "http://localhost:49743/api/movies";

    function init() {
        WinJS.UI.processAll().done(function () {
            // Get element and control references
            var lvMovies = document.getElementById("lvMovies").winControl;

            // Login to Windows Live
            var scopes = ["wl.signin"];
            WL.init({
                scope: scopes,
                redirect_uri: REDIRECT_DOMAIN
            });

            WL.login().then(
                function(response) {
                    // Get the authentication token
                    var authenticationToken = response.session.authentication_token;

                    // Call the web service
                    var options = {
                        url: WEBSERVICE_URL,
                        headers: { authenticationToken: authenticationToken }
                    };

                    WinJS.xhr(options).done(
                        function (xhr) {
                            var movies = JSON.parse(xhr.response);
                            var listMovies = new WinJS.Binding.List(movies);
                            lvMovies.itemDataSource = listMovies.dataSource;
                        },
                        function (xhr) {
                            console.log(xhr.statusText);
                        }
                    );
                },
                function(response) {
                    throw WinJS.ErrorFromName("Failed to login!");

                }
            );
        });
    }

    document.addEventListener("DOMContentLoaded", init);

})();
There are two constants which you need to set to get the code above to work: REDIRECT_DOMAIN and WEBSERVICE_URL. The REDIRECT_DOMAIN is the domain that you entered when registering your app with Live. The WEBSERVICE_URL is the path to your web service.
You can get the correct value for WEBSERVICE_URL by opening the Project Properties for the MovieApp.Services project, clicking the Web tab, and getting the correct URL. The port number is randomly generated. In my code, I used the URL  “http://localhost:49743/api/movies”.
clip_image009
Assuming that the user is logged into Windows 8 with a Live account, when the user runs the MovieApp, the user is logged into Live automatically. The user is logged in with the following code:

// Login to Windows Live
var scopes = ["wl.signin"];
WL.init({
   scope: scopes,
   redirect_uri: REDIRECT_DOMAIN
});
WL.login().then(function(response) {
  // Do something
});
The scopes setting determines what the user has permission to do. For example, access the user’s SkyDrive or access the user’s calendar or contacts. The available scopes are listed here:
http://msdn.microsoft.com/en-us/library/live/hh243646.aspx
In our case, we only need the wl.signin scope which enables Single Sign-On.
After the user signs in, you can retrieve the user’s Live authentication token. The authentication token is passed to the movies service to authenticate the user.

Creating the Movies Service

The Movies Service is implemented as an API controller in an ASP.NET MVC 4 Web API project. Here’s what the MoviesController looks like:

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using JWTSample;
using MovieApp.Services.Models;

namespace MovieApp.Services.Controllers
{
    public class MoviesController : ApiController
    {
        const string CLIENT_SECRET = "NtxjF2wu7JeY1unvVN-lb0hoeWOMUFoR";

        // GET api/values
        public HttpResponseMessage Get()
        {
            // Authenticate
            // Get authenticationToken
            var authenticationToken = Request.Headers.GetValues("authenticationToken").FirstOrDefault();
            if (authenticationToken == null)
            {
                return new HttpResponseMessage(HttpStatusCode.Unauthorized);
            }

            // Validate token
            var d = new Dictionary<int, string>();
            d.Add(0, CLIENT_SECRET);
            try
            {
                var myJWT = new JsonWebToken(authenticationToken, d);
            }
            catch
            {
                return new HttpResponseMessage(HttpStatusCode.Unauthorized);
            }

            // Return results
            return Request.CreateResponse(
                HttpStatusCode.OK,
                new List<Movie> {
                    new Movie {Title="Star Wars", Director="Lucas"},
                    new Movie {Title="King Kong", Director="Jackson"},
                    new Movie {Title="Memento", Director="Nolan"}
                }
            );
        }

    }

} 
Because the Metro app performs an HTTP GET request, the MovieController Get() action is invoked. This action returns a set of three movies when, and only when, the authentication token is validated.
The Movie class looks like this:

using Newtonsoft.Json;

namespace MovieApp.Services.Models
{
    public class Movie
    {
        [JsonProperty(PropertyName="title")]
        public string Title { get; set; }

        [JsonProperty(PropertyName="director")]
        public string Director { get; set; }
    }
}
Notice that the Movie class uses the JsonProperty attribute to change Title to title and Director to director to make JavaScript developers happy.
The Get() method validates the authentication token before returning the movies to the Metro app. To get authentication to work, you need to provide the client secret which you created at the Live management site. If you forgot to write down the secret, you can get it again here:
https://manage.dev.live.com/Applications/Index
The client secret is assigned to a constant at the top of the MoviesController class.
The MoviesController class uses a helper class named JsonWebToken to validate the authentication token. This class was created by the Windows Live team. You can get the source code for the JsonWebToken class from the following GitHub repository:
https://github.com/liveservices/LiveSDK/blob/master/Samples/Asp.net/AuthenticationTokenSample/JsonWebToken.cs
You need to add an additional reference to your MVC project to use the JsonWebToken class: System.Runtime.Serialization.
clip_image011
You can use the JsonWebToken class to get a unique and validated user ID like this:

var user = myJWT.Claims.UserId;
If you need to store user specific information then you can use the UserId property to uniquely identify the user making the web service call.

Running the MovieApp

When you first run the Metro MovieApp, you get a screen which asks whether the app should have permission to use Single Sign-On. This screen never appears again after you give permission once.
clip_image013
Actually, when I first ran the app, I get the following error:
clip_image014
According to the error, the app is blocked because “We detected some suspicious activity with your Online Id account. To help protect you, we’ve temporarily blocked your account.”
This appears to be a bug in the current preview release of the Live SDK and there is more information about this bug here:
http://social.msdn.microsoft.com/Forums/en-US/messengerconnect/thread/866c495f-2127-429d-ab07-842ef84f16ae/
If you click continue, and continue running the app, the error message does not appear again.


Summary

The goal of this blog entry was to describe how you can validate Metro apps and Metro users when performing a call to a remote web service. First, I explained how you can create a Metro app which takes advantage of Single Sign-On to authenticate the current user against Live automatically. You learned how to register your Metro app with Live and how to include an authentication token in an Ajax call.
Next, I explained how you can validate the authentication token – retrieved from the request header – in a web service. I discussed how you can use the JsonWebToken class to validate the authentication token and retrieve the unique user ID.

No comments:

Post a Comment

Could not find a part of the path ... bin\roslyn\csc.exe

I am trying to run an ASP.NET MVC (model-view-controller) project retrieved from TFS (Team Foundation Server) source control. I have added a...