Archive for November, 2008|Monthly archive page

The Netflix API Part 2 – Getting Authorization and Access

Please note that all keys, tokens and signatures in this series are made up and not usable.

Do not store your secret key in your application as swf files are not secure.

The first post in this series, The Netflix API Part 1 – Making Your Initial Request, was basically about OAuth and how to deal with it to get initial access to Netflix using your application.  Thankfully we’ve got the AS3 OAuth library from iotashan.com to deal with it.  However, it is necessary to understand the basic steps required for OAuth access to get going and it will help with many other mashable sites since it is a common way of gaining user authorization to protected assets.

Of course I’ve reworked ALL of the code from the last example but I won’t be as explicit in explaining what I covered in the last section.  Call it an Iteration.  ;-)   It is now a Flex example so you Flash people will have to adjust.  Shouldn’t be too hard.  Use URLLoader instead of HTTPService.

I’ve created a single MXML file for the project called NetflixAPITutorial.mxml.

The main imports and static variables from the previous section are as follows:

import flash.net.navigateToURL;
import mx.rpc.http.HTTPService;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import org.iotashan.oauth.*;

private static const CONSUMER_KEY:String = "INSERT YOUR CONSUMER KEY HERE";
private static const CONSUMER_SECRET:String = "INSERT YOUR CONSUMER SECRET HERE"
private static const NETFLIX_BASE_URL:String = "http://api.netflix.com/";
private static const SIG_METHOD:IOAuthSignatureMethod = new   OAuthSignatureMethod_HMAC_SHA1();

We’ll also need for the items from the last tutorial such as consumer, service, and request token. We’ll also need variables for the authorization url, the access token and the user id.

private var _consumer:OAuthConsumer;
private var _service:HTTPService;
private var _requestToken:OAuthToken;
private var _authUrl:String;
private var _accessToken:OAuthToken;
private var _userId:String;

Instead of using a bunch of different buttons, I’m going to use one button and change it’s function using a setButtonState method. It will get passed one of the following static state strings. These are in the order of what we’ll be doing in this tutorial.

  • Getting an initial request token
  • Sending the user to Netflix for authorization
  • Asking the user if authorization is complete
  • Getting user info
  • Getting the user’s queue
private static const BUTTON_STATE_REQUEST_TOKEN:String = "request_token";
private static const BUTTON_STATE_GET_AUTHORIZATION:String = "get_authorization";
private static const BUTTON_STATE_AUTHORIZATION_COMPLETE:String = "authorization_complete";
private static const BUTTON_STATE_GET_USER_INFO:String = "get_user_info";
private static const BUTTON_STATE_GET_USER_QUEUE:String = "get_user_queue";

Here are the methods for requesting the access token (don’t forget to add onCreationComplete() to the creationComplete Attribute of your application component tag).  This will get you to where we left off last time.

private function onCreationComplete():void
{
	_consumer = new OAuthConsumer(NetflixAPITutorial.CONSUMER_KEY, NetflixAPITutorial.CONSUMER_SECRET);
	setButtonState(NetflixAPITutorial.BUTTON_STATE_REQUEST_TOKEN);
}

private function getRequestToken_Click(event:MouseEvent):void
{
	getRequestToken();
}

private function getRequestToken():void
{
	_service = new HTTPService();
	_service.addEventListener(ResultEvent.RESULT, requestTokenResult);
	_service.addEventListener(FaultEvent.FAULT, requestTokenFault);

	var reqUrl:String = NETFLIX_BASE_URL + "oauth/request_token";
	var tokenRequest:OAuthRequest = new OAuthRequest("GET", reqUrl, null, _consumer);
	_service.url = tokenRequest.buildRequest(NetflixAPITutorial.SIG_METHOD);
	_service.send();
}

private function requestTokenResult(event:ResultEvent):void
{
	setButtonState(NetflixAPITutorial.BUTTON_STATE_GET_AUTHORIZATION);
	debug(event.result as String);
}

private function requestTokenFault(event:FaultEvent):void
{
	debug("requestTokenFault() " + event.fault.message);
}

private function setButtonState(state:String):void
{
	switch(state)
	{
		case NetflixAPITutorial.BUTTON_STATE_REQUEST_TOKEN:
			btnRequest.label = "Request Token";
			btnRequest.addEventListener(MouseEvent.CLICK, getRequestToken_Click);
			break;
	}
}

private function debug(message:String):void
{
	txtDebug.text += message + "\n\n";
}

And here are your TextArea and Button that go after the script tag:

<mx:TextArea id="txtDebug" width="100%" height="100%" />
<mx:Button id="btnRequest" />

Run the app and you’ll see that you get your token request.

Now the data that is returned is a new oauth_token key and a new oauth_token_secret along with an application name and an url to send the user to to request permission to access their data on Netflix. We want to create a new oauth request token with the new token key and secret.  To send the user to Netflix to allow access we also need to append the application name and consumer key to the request.  We’ll create a parseRequestToken method and call it from the requestTokenResult method.

Here is parseRequestToken:

private function parseTokenRequest(result:String):void
{
	var aResult:Array = result.split("&");
	var split:Array;
	var oResult:Object = new Object();
	for each(var item:String in aResult)
	{
		split = item.split("=");
		oResult[split[0]] = split[1];
	}

	_requestToken = new OAuthToken(oResult.oauth_token, oResult.oauth_token_secret);
	_loginUrl = unescape(oResult.login_url as String) + "&application_name=" + oResult.application_name + "&oauth_consumer_key=" + NetflixAPITutorial.CONSUMER_KEY;
}

It splits on the ampersand character and then parses the resulting array splitting on the equals (=) character. Since the returned values are known we can use these as property values on the oResult object and retrieve them to create and save the new requestToken and loginUrl with the application_name and oauth_consumer_key values appended to the query string.

The requestTokenResult with the parseTokenResult call now looks like this:

private function requestTokenResult(event:ResultEvent):void
{
	parseTokenRequest(event.result as String);
	setButtonState(NetflixAPITutorial.BUTTON_STATE_GET_AUTHORIZATION);
	debug(event.result as String);
}

Getting Authorization from  the User

First we’ll need to add the new button state case to the setButtonStateMethod:

case NetflixAPITutorial.BUTTON_STATE_GET_AUTHORIZATION:
	btnRequest.label = "Get User Authorization";
	btnRequest.removeEventListener(MouseEvent.CLICK, getRequestToken_Click);
	btnRequest.addEventListener(MouseEvent.CLICK, getAuthorization_Click);
	break;

Now add the getAuthorization_Click, getAuthorization methods.  We’ll also add another case to the getButtonState method.  getAuthorization will take the user to the Netflix site for to authorize your application. When the user is finished they return to the application and select the “Authorization Complete” button.

private function getAuthorization_Click(event:MouseEvent):void
{
	getAuthorization();
}

private function getAuthorization():void
{
	var req:URLRequest = new URLRequest(_loginUrl);
	navigateToURL(req);
	setButtonState(NetflixAPITutorial.BUTTON_STATE_AUTHORIZATION_COMPLETE);
}

private function authorizationComplete_Click(event:MouseEvent):void
{
    //code to come in the next section
}

Add this to the setButtonState method:

case NetflixAPITutorial.BUTTON_STATE_AUTHORIZATION_COMPLETE:btnRequest.label = "Authorization Complete";
	btnRequest.removeEventListener(MouseEvent.CLICK, getAuthorization_Click);
        btnRequest.addEventListener(MouseEvent.CLICK, authorizationComplete_Click);
	break;

Go ahead and run this code (authorize your own Netflix account) and then we’ll get to what to do after the user grants authorization.

Requesting an Access Token

Now that the application has been authorized we need to request an access token.

Add getAccessToken() to the authorizationComplete method and add the getAccessToken method and call as well as it’s result, fault and parse methods as shown below.

private function getAuthorization():void
{
    var req:URLRequest = new URLRequest(_loginUrl);
    navigateToURL(req);
    setButtonState(NetflixAPITutorial.BUTTON_STATE_AUTHORIZATION_COMPLETE);
}

private function authorizationComplete_Click(event:MouseEvent):void
{
    getAccessToken();
}

private function getAccessToken():void
{
    _service = new HTTPService();
    _service.addEventListener(ResultEvent.RESULT, accessTokenResult);
    _service.addEventListener(FaultEvent.FAULT, accessTokenFault);

    var tokenRequest:OAuthRequest = new OAuthRequest("GET", NetflixAPITutorial.NETFLIX_BASE_URL + "oauth/access_token", null, _consumer, _requestToken);
    _service.url = tokenRequest.buildRequest(NetflixAPITutorial.SIG_METHOD);
    _service.send();
}

private function accessTokenResult(event:ResultEvent):void
{
    debug(event.result as String);
    parseAccessTokenRequest(event.result as String);
}

private function accessTokenFault(event:FaultEvent):void
{
    debug("accessTokenFault() " + event.fault.message);
}

private function parseAccessTokenRequest(result:String):void
{
	var aResult:Array = result.split("&");
	var split:Array;
	var oResult:Object = new Object();
	for each(var item:String in aResult)
	{
		split = item.split("=");
		oResult[split[0]] = split[1];
	}

	_accessToken = new OAuthToken(oResult.oauth_token, oResult.oauth_token_secret);
	_userId = oResult.user_id;
}

Note that in the getAccessTokenMethod the new OAuthRequest call uses a path of “oauth/access_token” and uses the new _requestToken value.  In the parseAccessTokenRequest method you can see that we use the returned oauth_token and oauth_token_secret to create a new OAuthToken and store it in the _accessToken property.  We’ll also store the returned user_id value in the _userId property.

Run this code and check out the result and then we’ll finally get to the good stuff.

Getting User Information from Netflix

With the access token and the userID we can now really start to work with the user’s data on Netflix.  Lets start by getting the user’s information.

First add a Get User Info button state to the setButtonStateMethod

case NetflixAPITutorial.BUTTON_STATE_GET_USER_INFO:
    btnRequest.label = "Get User Info";
    btnRequest.removeEventListener(MouseEvent.CLICK, authorizationComplete_Click);
    btnRequest.addEventListener(MouseEvent.CLICK, getUserInfo_Click);

Add the setButtonStateMethod call to the accessTokenResult method.

private function accessTokenResult(event:ResultEvent):void
{
    debug(event.result as String);
    parseAccessTokenRequest(event.result as String);
    setButtonState(NetflixAPITutorial.BUTTON_STATE_GET_USER_INFO);
}

Now we’ll fill out the authorizationComplete_Click method, add the authorizationComplete, getUserInfo, userInfoResult and userInfoFault methods.

private function getUserInfo_Click(event:MouseEvent):void
{
     getUserInfo();
}

private function getUserInfo():void
{
     _service = new HTTPService();
     _service.addEventListener(ResultEvent.RESULT, userInfoResult);
     _service.addEventListener(FaultEvent.FAULT, userInfoFault);

     var req:OAuthRequest = new OAuthRequest("GET", NetflixAPITutorial.NETFLIX_BASE_URL + "users/" + _userId, null, _consumer, _accessToken);
     _service.url = req.buildRequest(new OAuthSignatureMethod_HMAC_SHA1(), OAuthRequest.RESULT_TYPE_URL_STRING);
     _service.send();
}

private function userInfoResult(event:ResultEvent):void
{
     debug("userInfoResult()\n\n" + event.result);
}

private function userInfoFault(event:FaultEvent):void
{
     debug("userInfoFault() " + event.fault.message);
}

In the getUserInfoMethod you’ll see that when we create the OAuthRequest that we’re appending “users/” and the _userId value to the base url. Go ahead and run this code and see what you get.

You’ll see that you get “[object Object]” back.  Let’s check this out by putting a breakpoint in the userInfoResult method.  Run the app again.  When you hit the break points check the event.result value.  You’ll see that it’s of type ProxyObject and that you can see all the properties in there.  The reality is that the returned value is actually XML.  You can try to deal with the proxy object but trust me it won’t be fun.  We can tell the HTTPService instance that we are going to get XML back by setting the the resultFormat property to E4X.  getUserInfo will now look like this:

private function getUserQueue():void
{
    _service = new HTTPService();
    _service.resultFormat = "e4x"
    _service.addEventListener(ResultEvent.RESULT, userQueueResult);
    _service.addEventListener(FaultEvent.FAULT, userQueueFault);

    var req:OAuthRequest = new OAuthRequest("GET", NetflixAPITutorial.NETFLIX_BASE_URL + "users/" + _userId + "/queues/disc", null, _consumer, _accessToken);
    _service.url = req.buildRequest(new OAuthSignatureMethod_HMAC_SHA1(), OAuthRequest.RESULT_TYPE_URL_STRING);
    _service.send();
}

Remove the breakpoint, build and run again.

You’ll see some sweet XML printed to the debug field.  Rock!  This is something you can use.  Of course I won’t tell you what to do with it but with the access token, your consumer key and the proper url, which you can find in the Netflix API documentation you’re sure to be laughing.

Getting the User’s Queue from Netflix

I won’t go into much detail on this but really all you need is to use the correct url in the OAuthRequest to get the queue.  Add the button state and call, then the usual click, request, result and fault methods.

The setButtonState case:

case NetflixAPITutorial.BUTTON_STATE_GET_USER_QUEUE:
	btnRequest.label = "Get User Queue";
	btnRequest.removeEventListener(MouseEvent.CLICK, getUserInfo_Click);
	btnRequest.addEventListener(MouseEvent.CLICK, getUserQueue_Click);
	break;

The rest of the methods:

private function getUserQueue_Click(event:MouseEvent):void
{
	getUserQueue();
}

private function getUserQueue():void
{
	_service = new HTTPService();
	_service.resultFormat = "e4x"
	_service.addEventListener(ResultEvent.RESULT, userQueueResult);
	_service.addEventListener(FaultEvent.FAULT, userQueueFault);

	var req:OAuthRequest = new OAuthRequest("GET", NetflixAPITutorial.NETFLIX_BASE_URL + "users/" + _userId + "/queues/disc", null, _consumer, _accessToken);
	_service.url = req.buildRequest(new OAuthSignatureMethod_HMAC_SHA1(), OAuthRequest.RESULT_TYPE_URL_STRING);
	_service.send();
}

private function userQueueResult(event:ResultEvent):void
{
	debug("userQueueResult(): \n\n" + event.result);
}

private function userQueueFault(event:FaultEvent):void
{
	debug("userQueueFault(): " + event.fault.message);
}

In the next post in this series I’ll talk about how to deal with managing access for the users of your application.

Here’s all of the source code for this tutorial:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="onCreationComplete()">
	<mx:Script>
		<![CDATA[
			import flash.net.navigateToURL;
			import mx.rpc.http.HTTPService;
			import mx.rpc.events.FaultEvent;
			import mx.rpc.events.ResultEvent;
			import org.iotashan.oauth.*;

			private static const CONSUMER_KEY:String = "[Insert your key here]";
			private static const CONSUMER_SECRET:String = "[Insert your secret here]"
			private static const NETFLIX_BASE_URL:String = "http://api.netflix.com/";
			private static const SIG_METHOD:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();

			private var _consumer:OAuthConsumer;
			private var _service:HTTPService;
			private var _requestToken:OAuthToken;
			private var _loginUrl:String;
			private var _accessToken:OAuthToken;
			private var _userId:String;

			private static const BUTTON_STATE_REQUEST_TOKEN:String = "request_token";
			private static const BUTTON_STATE_GET_AUTHORIZATION:String = "get_authorization";
			private static const BUTTON_STATE_AUTHORIZATION_COMPLETE:String = "authorization_complete";
			private static const BUTTON_STATE_GET_USER_INFO:String = "get_user_info";
			private static const BUTTON_STATE_GET_USER_QUEUE:String = "get_user_queue";

			private function onCreationComplete():void
			{
				_consumer = new OAuthConsumer(NetflixAPITutorial.CONSUMER_KEY, NetflixAPITutorial.CONSUMER_SECRET);
				setButtonState(NetflixAPITutorial.BUTTON_STATE_REQUEST_TOKEN);
			}

			private function getRequestToken_Click(event:MouseEvent):void
			{
				getRequestToken();
			}

			private function getRequestToken():void
			{
				_service = new HTTPService();
				_service.addEventListener(ResultEvent.RESULT, requestTokenResult);
				_service.addEventListener(FaultEvent.FAULT, requestTokenFault);

				var reqUrl:String = NETFLIX_BASE_URL + "oauth/request_token";
				var tokenRequest:OAuthRequest = new OAuthRequest("GET", reqUrl, null, _consumer);
				_service.url = tokenRequest.buildRequest(NetflixAPITutorial.SIG_METHOD);
				_service.send();
			}

			private function requestTokenResult(event:ResultEvent):void
			{
				parseTokenRequest(event.result as String);
				setButtonState(NetflixAPITutorial.BUTTON_STATE_GET_AUTHORIZATION);
				debug(event.result as String);
			}

			private function requestTokenFault(event:FaultEvent):void
			{
				debug("requestTokenFault() " + event.fault.message);
			}

			private function parseTokenRequest(result:String):void
			{
				var aResult:Array = result.split("&");
				var split:Array;
				var oResult:Object = new Object();
				for each(var item:String in aResult)
				{
					split = item.split("=");
					oResult[split[0]] = split[1];
				}

				_requestToken = new OAuthToken(oResult.oauth_token, oResult.oauth_token_secret);
				_loginUrl = unescape(oResult.login_url as String) + "&application_name=" + oResult.application_name + "&oauth_consumer_key=" + NetflixAPITutorial.CONSUMER_KEY;
			}

			private function getAuthorization_Click(event:MouseEvent):void
			{
				getAuthorization();
			}

			private function getAuthorization():void
			{
				var req:URLRequest = new URLRequest(_loginUrl);
				navigateToURL(req);
				setButtonState(NetflixAPITutorial.BUTTON_STATE_AUTHORIZATION_COMPLETE);
			}

			private function authorizationComplete_Click(event:MouseEvent):void
			{
				authorizationComplete();
			}

			private function authorizationComplete():void
			{
				getAccessToken();
			}

			private function getAccessToken():void
			{
				_service = new HTTPService();
				_service.addEventListener(ResultEvent.RESULT, accessTokenResult);
				_service.addEventListener(FaultEvent.FAULT, accessTokenFault);

				var tokenRequest:OAuthRequest = new OAuthRequest("GET", NetflixAPITutorial.NETFLIX_BASE_URL + "oauth/access_token", null, _consumer, _requestToken);
				_service.url = tokenRequest.buildRequest(NetflixAPITutorial.SIG_METHOD);
				_service.send();
			}

			private function accessTokenResult(event:ResultEvent):void
			{
				debug(event.result as String);
				parseAccessTokenRequest(event.result as String);
				setButtonState(NetflixAPITutorial.BUTTON_STATE_GET_USER_INFO);
			}

			private function accessTokenFault(event:FaultEvent):void
			{
				debug("accessTokenFault() " + event.fault.message);
			}

			private function parseAccessTokenRequest(result:String):void
			{
				var aResult:Array = result.split("&");
				var split:Array;
				var oResult:Object = new Object();
				for each(var item:String in aResult)
				{
					split = item.split("=");
					oResult[split[0]] = split[1];
				}

				_accessToken = new OAuthToken(oResult.oauth_token, oResult.oauth_token_secret);
				_userId = oResult.user_id;
			}

			private function getUserInfo_Click(event:MouseEvent):void
			{
				getUserInfo();
			}

			private function getUserInfo():void
			{
				_service = new HTTPService();
				_service.resultFormat = "e4x"
				_service.addEventListener(ResultEvent.RESULT, userInfoResult);
				_service.addEventListener(FaultEvent.FAULT, userInfoFault);

				var req:OAuthRequest = new OAuthRequest("GET", NetflixAPITutorial.NETFLIX_BASE_URL + "users/" + _userId, null, _consumer, _accessToken);
				_service.url = req.buildRequest(new OAuthSignatureMethod_HMAC_SHA1(), OAuthRequest.RESULT_TYPE_URL_STRING);
				_service.send();
			}

			private function userInfoResult(event:ResultEvent):void
			{
				debug("userInfoResult()\n\n" + event.result);
				setButtonState(NetflixAPITutorial.BUTTON_STATE_GET_USER_QUEUE);
			}

			private function userInfoFault(event:FaultEvent):void
			{
				debug("userInfoFault() " + event.fault.message);
			}

			private function getUserQueue_Click(event:MouseEvent):void
			{
				getUserQueue();
			}

			private function getUserQueue():void
			{
				_service = new HTTPService();
				_service.resultFormat = "e4x"
				_service.addEventListener(ResultEvent.RESULT, userQueueResult);
				_service.addEventListener(FaultEvent.FAULT, userQueueFault);

				var req:OAuthRequest = new OAuthRequest("GET", NetflixAPITutorial.NETFLIX_BASE_URL + "users/" + _userId + "/queues/disc", null, _consumer, _accessToken);
				_service.url = req.buildRequest(new OAuthSignatureMethod_HMAC_SHA1(), OAuthRequest.RESULT_TYPE_URL_STRING);
				_service.send();
			}

			private function userQueueResult(event:ResultEvent):void
			{
				debug("userQueueResult(): \n\n" + event.result);
			}

			private function userQueueFault(event:FaultEvent):void
			{
				debug("userQueueFault(): " + event.fault.message);
			}

			private function saveToken(token:OAuthToken, key:String):void
			{

			}

			private function getToken(key:String):OAuthToken
			{
				return null;
			}

			private function setButtonState(state:String):void
			{
				switch(state)
				{
					case NetflixAPITutorial.BUTTON_STATE_REQUEST_TOKEN:
						btnRequest.label = "Request Token";
						btnRequest.addEventListener(MouseEvent.CLICK, getRequestToken_Click);
						break;

					case NetflixAPITutorial.BUTTON_STATE_GET_AUTHORIZATION:
						btnRequest.label = "Get User Authorization";
						btnRequest.removeEventListener(MouseEvent.CLICK, getRequestToken_Click);
						btnRequest.addEventListener(MouseEvent.CLICK, getAuthorization_Click);
						break;

					case NetflixAPITutorial.BUTTON_STATE_AUTHORIZATION_COMPLETE:
						btnRequest.label = "Authorization Complete";
						btnRequest.removeEventListener(MouseEvent.CLICK, getAuthorization_Click);
						btnRequest.addEventListener(MouseEvent.CLICK, authorizationComplete_Click);
						break;

					case NetflixAPITutorial.BUTTON_STATE_GET_USER_INFO:
						btnRequest.label = "Get User Info";
						btnRequest.removeEventListener(MouseEvent.CLICK, authorizationComplete_Click);
						btnRequest.addEventListener(MouseEvent.CLICK, getUserInfo_Click);
						break;

					case NetflixAPITutorial.BUTTON_STATE_GET_USER_QUEUE:
						btnRequest.label = "Get User Queue";
						btnRequest.removeEventListener(MouseEvent.CLICK, getUserInfo_Click);
						btnRequest.addEventListener(MouseEvent.CLICK, getUserQueue_Click);
						break;
				}
			}

			private function debug(message:String):void
			{
				txtDebug.text += message + "\n\n";
			}

		]]>
	</mx:Script>

	<mx:TextArea id="txtDebug" width="100%" height="100%" />
	<mx:Button id="btnRequest" />
</mx:WindowedApplication>