Tutorials, Videos

AIR and LinkedIn

20 Comments 31 January 2011

AIR and LinkedIn

Hey guys… long time no see :) I had to play a little bit with the LinkedIn API based on a classic REST architecture and on OAuth (Open Authorization) to identify the users. Browsing the content of LinkedIn developers forum, I was amazed by the number of developers who are trying to access the LinkedIn API from Adobe AIR. Reading the threads, a lot of them failed. That’s why I’m sharing the source code of a minimalistic AIR app that retrieves your LinkedIn contacts.

The hardest part is definitely the authentification using OAuth. Once identified, you can use the LinkedIn API which is unfortunately very limited. You can display your network updates, post a status, comment an update, view your contacts, search for someone in linkedin and invite him to join your network. But you CANNOT read your inbox messages and accept your linkedIn invitations. I guess that LinkedIn wants you to visit their website to achieve these very basic and useful tasks.

Before coding anything, you need to request an API key here: http://developer.linkedin.com/community/apis First, you must declare your AIR application. You’ll obtain two keys for your AIR application, the API key and the secret key. You also need to indicate on this page the Redirect URL. This one is very very important to create a seamless authentification workflow. I’ll cover it later in this article.

The OAuth Authentification Flow

This is the OAuth flow for LinkedIn. The consumer on the left is my Adobe AIR application. First, the application downloads a “Request token”. Thanks to this token, the linkedIn website lets you log-in and decides if you authorizes or not my AIR app to access your LinkedIn information. If yes, the AIR app receives an Access Token and can finally access the linkedIn API. I figured out that you can easily store this token and use it several weeks. To communicate with the OAuth, I rely on this open-source project: http://code.google.com/p/oauth-as3/ It’s the most advanced AS3 library for Open Authentification. I also used Christophe’s article on OAuth and TripIT as a strating point (although the linkedIn API is a little bit more complex): http://coenraets.org/blog/2010/12/flex-and-tripit-integration-example-with-oauth-authorization/

The User Experience

Here is a video of the final AIR application running on my MAC.

The Code

Variables

import org.iotashan.oauth.*;
 
protected var consumerKey:String = "YOUR_KEY";
protected var consumerSecret:String = "YOUR_SECRET_KEY";
 
protected var requestTokenUrl:String = "https://api.linkedin.com/uas/oauth/requestToken";
protected var authorizeUrl:String = "https://www.linkedin.com/uas/oauth/authorize";
 
//This value is mandatory, but linkedIn will take the URL provided in your settings on linkedin.com
protected var authorizeCallbackUrl:String = "http://www.YOURSERVER.com/YOUR_PAGE.html";
 
protected var accessTokenUrl:String = "https://api.linkedin.com/uas/oauth/accessToken";
 
protected var token:OAuthToken = new OAuthToken();
protected var consumer:OAuthConsumer = new OAuthConsumer(consumerKey, consumerSecret);
protected var signatureMethod:OAuthSignatureMethod_HMAC_SHA1 = new OAuthSignatureMethod_HMAC_SHA1();
 
protected var oauthVerifier:String = "";

Init(), is there any token ?

Once the application created, I test if a token is stored in the Encrypted Local Storage. A token is an object with two string variables, keySecret and keyToken. If the storage is empty, I request a new token (getRequestToken). If I obtain a token, I test if it’s still valid trying to access some profile information. I still need to enhance this portion to see if I catch an HTTP error from LinkedIn.

protected function windowedapplication1_creationCompleteHandler(event:FlexEvent):void
			{
 
				// First launch, try to load the stored token
				var bytes:ByteArray = EncryptedLocalStore.getItem("keyToken");
				var bytes2:ByteArray = EncryptedLocalStore.getItem("keySecret");
				if(bytes != null){
					token.key = bytes.readObject() as String;
					token.secret = bytes2.readObject() as String;
 
					trace(token.key + " /// " + token.secret);
 
					//test if the token is still authorized
					testToken();
				}else{
					trace("there is no token stored");
					// we need to request a token first
					getRequestToken();
				}
			}
 
			private function testToken():void
			{
				// We'll send a classic request to LinkedIn and see if we get a result or not
				// to test the loaded token
				var loaderTest:URLLoader = new URLLoader();
				loaderTest.addEventListener(Event.COMPLETE, resultHandlerTest);
				loaderTest.addEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS, httpResponse);
				var request:OAuthRequest = new OAuthRequest("GET", "https://api.linkedin.com/v1/people/~", null, consumer, token);
				loaderTest.load(new URLRequest(request.buildRequest(signatureMethod)));
 
			}
 
			protected function httpResponse(event:HTTPStatusEvent):void
			{
				// TODO Auto-generated method stub
				trace("HTTP error?");
			}
 
			private function resultHandlerTest(event:Event):void
			{
				// TODO Auto Generated method stub
				var thelist:XML = new XML(event.currentTarget.data);
				if(thelist..headline != null){
					//we can go on and directly call LinkedIn APIs
					currentState = "listResults";
					sendRequest('http://api.linkedin.com/v1/people/~/connections');
				}else{
					//We need a new token
					trace("we need a new token");
					getRequestToken();
				}
				trace("ok");
			}

Ask for a Request token

Now we just have to follow the flow suggested by LinkedIn. First, we just ask for a Request token.

protected function getRequestToken():void
			{
				// First step with Oauth. Get Request Token.
 
				var loader:URLLoader = new URLLoader();
				loader.addEventListener(Event.COMPLETE, requestTokenHandler);
 
				var request:OAuthRequest = new OAuthRequest("GET", requestTokenUrl, null, consumer, null);
				loader.load(new URLRequest(request.buildRequest(signatureMethod)));
			}
 
			protected function requestTokenHandler(event:Event):void
			{
				// We received a token from LinkedIn. Let's analyze it.
				parseToken(event.currentTarget.data);
				currentState = "linkHTML";
				// Now we need to authorize our token so that our AIR app can access the API
				authorizeToken();
			}

The parse token function is the one created by Christophe for his TripIt application.

protected function parseToken(response:String):void
			{
				// parse the requested token to extract the keys
				var params:Array = response.split("&");
				for (var i:int = 0; i < params.length; i++)
				{
					var param:String = params[i];
					var nameValue:Array = param.split("=");
					if (nameValue.length == 2)
					{
						switch (nameValue[0])
						{
							case "oauth_token":
								token.key = nameValue[1];
								break;
							case "oauth_token_secret":
								token.secret = nameValue[1];
								break;
						}
					}
				}
			}

The user must authorize your application

Now, here is the tricky part. As I need to display LinkedIn HTML pages (login screen and grant access screen), I’ll use the HTML AIR component to display the pages directly within my application. Once the application authorized by the user, it will redirect the browser to a URL (the Redirect URL provided in the LinkedIn API settings page for your application). That’s why I’m listening at a LocationChange event.

protected function authorizeToken():void
			{
 
				//navigateToURL(new URLRequest(authorizeUrl + "?oauth_token=" + token.key + "&oauth_callback=" + authorizeCallbackUrl));
				HTMLcontainer.location = authorizeUrl + "?oauth_token=" + token.key + "&oauth_callback=" + authorizeCallbackUrl;
				HTMLcontainer.addEventListener(Event.LOCATION_CHANGE, onLocationChange);
			}

Then, if a Location Change happens, I test the root of the URL to check that LinkedIn is actually redirecting the browser to my website. For my sample, I placed an HTML page on riagora.com. That’s why I’m testing this substring. The goal is to catch the last five characters of the URL. It contains the oAuth Verifier code. This code must be sent back to LinkedIn to access the API.

protected function onLocationChange(event:Event):void
			{
				// TODO Auto-generated method stub
				trace("stop");
				// check if http://www.riagora.com/linnkedIn.html?oauth_token=98166133-8290-427e-a038-8fa2714b7657&oauth_verifier=77123
				var theUrl:String = event.currentTarget.location as String;
				trace(theUrl);
				trace(theUrl.substr(0,22));
				if(theUrl.substr(0,22) == "http://www.riagora.com"){
					trace("ok");
					//get the oauth_verifier - the last five characters
					oauthVerifier = theUrl.substr(theUrl.length - 5, theUrl.length);
					trace (oauthVerifier);
					//we have the oauthVerifier code. We can now get the Access API token
					getAccessToken();
				}
			}

The Access Token

To access the API, we need to download another token from LinkedIn. You must extend the classic behavior of the OAuth library to add two new parameters in the request URL: the OAuth Verifier code, and the OAuth version (mandatory). Then I receive the token and I can store it in the Encrypted Local Storage for the next launches of the application.

protected function getAccessToken():void
			{
				currentState = "listResults";
				var loader2:URLLoader = new URLLoader();
				loader2.addEventListener(Event.COMPLETE, accessTokenHandler);
				var theVerifierCode:String = oauthVerifier;
				var request:OAuthRequest = new OAuthRequest("GET", accessTokenUrl,{oauth_verifier:theVerifierCode,oauth_version:'1.0'} , consumer, token);
				loader2.load(new URLRequest(request.buildRequest(signatureMethod)));
			}
 
			protected function accessTokenHandler(event:Event):void
			{
 
				parseToken(event.currentTarget.data);
				//yeah ! We have a valid token from LinkedIn. Let's store it
				saveToken();
 
				//launch first request
				sendRequest('http://api.linkedin.com/v1/people/~/connections');
			}
 
			protected function saveToken():void{
				//saving both keys in the Encrypted Local Storage
				// for mobile apps, just use a classic Local Shared Object
				var bytes:ByteArray = new ByteArray();
				bytes.writeObject(token.key);
				EncryptedLocalStore.setItem("keyToken",bytes);
 
				var bytes2:ByteArray = new ByteArray();
				bytes2.writeObject(token.secret);
				EncryptedLocalStore.setItem("keySecret",bytes2);
			}

REST API

Now, feel free to call the LinkedIn REST API. The documentation is at the bottom of this page: http://developer.linkedin.com/community/apis When you call the REST API, you just need to pass your consumer key and the access token.

protected function sendRequest(url:String):void
			{
				var loader:URLLoader = new URLLoader();
				loader.addEventListener(Event.COMPLETE, resultHandler);
 
				var request:OAuthRequest = new OAuthRequest("GET", url, null, consumer, token);
				loader.load(new URLRequest(request.buildRequest(signatureMethod)));
			}
 
			protected function resultHandler(event:Event):void
			{
				var thelist:XML = new XML(event.currentTarget.data);
				list.dataProvider = new XMLListCollection(new XML(event.currentTarget.data)..person);
			}

MOBILE considerations

If you plan to develop a mobile application connected to LinkedIn using Flex 4.5 for instance, don’t use the Encrypted Local Storage (it’s not available in the AIR for Android API). Just use a Local shared object to store your token. The HTML component should be replace by the StageWeb API. Here a link to an excellent tutorial that explains how you can use StageWeb and OAuth: http://cookbooks.adobe.com/post_Accessing_a_OAuth_secured_API_via_Flash_Actionscri-18036.html

Links

The link to the source code of this project which also contains the AIR app: http://riagora.com/pvt_content/android/linkedInAIR.zip

Post to Twitter

Your Comments

20 Comments so far

  1. Mark Doherty says:

    Great job Michael!

    I would suggest that developers use StageWebView for all platforms.

    This is the approach I used in the Radar application, as soon as StageWebView was available in AIR 2.5.

    Mark

  2. Nice tutorial. Big time saver.

    One note:
    It was not clear to me that the oauth_callback paramater has to be set in the getRequestToken function….Once I figured that out everything worked great.

    Per the OAuth specs, if this parameter is left out of the reqest, the user is prompted to enter a “Pin”. For everybody’s edification an example change:

    var request:OAuthRequest = new OAuthRequest(“GET”, requestTokenUrl, {oauth_callback:’http://www.sj24.com’}, consumer, null);
    trace(request.buildRequest(signatureMethod));

  3. Ollie says:

    Thanks for the example.

    I have a problem with the loader in getRequestToken failing with 2032 errors:

    “Error #2032: Stream Error. URL: https://api.linkedin.com/uas/oauth/requestToken?oauth_consumer_key=XXXXX…”

    I realise it’s not your OAuth library but I wondered if you’d encountered this or had any suggestions?

  4. admin says:

    Yep… all the time. This is the classic error message. don’t forget that the oauth_callball url parameter is mandatory… And use GET http please…

  5. Ollie says:

    Thanks for your reply. I’ve now tried with GET and the oauth_callback parameter as shown in Tyler’s code but still get the same error. Funny thing is the url works in a browser.

    I’ve reported the issue on the oauth library page, http://code.google.com/p/oauth-as3/issues/detail?id=12

    I’d be very grateful for pointers to anything else I might try, or a guide to debugging this. I’ve not dealt with this error before.

  6. Dipock says:

    Thank’s for the post. I’m currently trying to figure out how to remove the StageWebView in an elegant manner. After the user has successfully granted access to the application, the callback URL is used to navigate the user to a ‘success screen’. However I have not managed to provide a robust means to remove the window and return you to the application. I’m running this on a tablet app and don’t want to navigate the user to the browser on the device – but to run this all in context of the app. (I tried a window close on the browser page itself but that didn’t work). I’m currently detecting when the URL location has changed – and then counting the number of times it changes (with each URL request) before disposing of the window. Clunky… Any ideas? Thanks.

  7. admin says:

    @Dipock I think you can redirect to a page on your own server. Maybe this page could send a JavaScript command, and maybe the ExternalInterface could catch this message…

  8. Dipock says:

    Thanks – I think I was on the right track – but didn’t think about the ExternalInterface to catch. Will give it a try and let you know.

  9. Hi,

    thanks for the nice tutorial, how can I issue a share POST on linkedin network? I was reading the code of OAuthRequest class and I see that it needs that the requestParams object must be a simple object with name value pairs. That smells like I can’t post shares, am I wrong?

  10. onix says:

    When I try with:

    var ldr:HTMLLoader = new HTMLLoader;
    ldr.load(urlReq);

    in stead of HTMLcontainer (wich is not recognized in AIR2.6) then I get the following message returned from the linkedIn api: “make sure you have cookies enabled”.
    Altough ldr.manageCookies are default true…

    Any ideas?

  11. Justin says:

    I got this up and running alright, next to Twitter & Facebook implementations.

    However, I still have a challenge posting updates to LinkedIn.

    The example API call works allright, but implementing a successful update is hard.

    Any idea on how to accomplish this?

  12. Divya says:

    Hi Michael,

    Is there a way to read my messages from linkedin. I figured out that we can send messages but was not able to get data for seeing the messages in inbox.

  13. Chetan says:

    This post has been immensely helpful on an app I am developing for the PlayBook.

    Everything works fine… BUT!! Facing a weird issue (only on the device, works fine on desktop) – When the Authentication (login) screen for LinkedIn comes up I am unable to see what I type into the Email/Password fields until the fields lose focus!

    Have tested it on both Tablet OS 2.0 and OS 1.0.8.4985 and tried StageWebView as well as QNXStageWebView and the problem persists in all cases. I’m using AIR 2.7 (FB 4.5.1) for development.

    Anyone face such an issue? Any help would be appreciated.

    Keep up the great work!

  14. Rajesh Sharma says:

    Hi,

    It’s nice example to start OAuth.
    But I am having hard time to POST updates in network for linkedIn.
    Any one successfully Posted in network with Adobe AIR, Please help !!!

  15. Pope says:

    Nice article…

    How can you implement this on a Web based Flex Application..

    Problem I am having is getting the Login popup to return that Authorization back to Flex app…

  16. juanp says:

    I imagine its a change by LinkedIn.. but the getAccessToken() needs to be done with a POST, not a GET. I set the POST twice, although perhaps only the second is needed. (Setting POST in the OAuthRequest doesn’t change URLRequest to POST)

    var request:OAuthRequest = new OAuthRequest(“POST”, accessTokenUrl,{oauth_verifier:theVerifierCode,oauth_version:’1.0′} , consumer, token);
    var url:URLRequest =new URLRequest(request.buildRequest(signatureMethod ))
    url.method = “POST”;

  17. Simple flow and good explanation.

    This helped me to implement linkedin integration for my colleague – I thought to play around.

    Keep it up!


Trackbacks/Pingbacks

  1. Tweets that mention AIR and LinkedIn | RIAgora -- Topsy.com - January 31, 2011

    […] This post was mentioned on Twitter by Michael Chaize, Jean-Marc Le Roux, Mathieu Isaia, Juraj Michalek, creacog and others. creacog said: RT @mchaize: AIR, LinkedIn and OAuth. Tutorial and source code. http://www.riagora.com/2011/01/air-and-linkedin/ […]

  2. Adobe — наш верный друг » AIR и LinkedIn — блог о air, flash, flex и других технологиях Adobe - January 31, 2011

    […] тут Мик Чайзе рассказывает о том, как скоммутировать […]

  3. AIR and LinkedIn - January 31, 2011

    […] and AS3) to the LinkedIn API. Special focus on the OAuth – open authentification- mechanism…. [full post] admin RIAgora tutorialsvideosairapi 0 0 0 0 0 […]

Share your view

Post a comment

Who am I ?

I'm Michaël CHAIZE, Adobe Flash Platform Evangelist based in Paris. I'm a big fan of Rich Internet Applications and I promote the Flash Platform in the Enterprise world.
You can follow me on twitter: http://twitter.com/mchaize

Magazine

Follow us on Facebook

© 2018 RIAgora. Powered by WordPress.

Daily Edition Theme by WooThemes - Premium WordPress Themes