- 2014-03-16 - Licence Fondamentale d'Informatique
vendredi 21 mars 2014

PHP: Step-by-step OAuth for Dummies based on LinkedIn API (Part II)

Part II: Getting LinkedIn Access Token

This guide is a follow up from Part I: Redirecting User to LinkedIn. At this point, it is assume that we have gotten our LinkedIn Consumer Key and Consumer Secret as PHP constants LINKEDIN_KEY and LINKEDIN_SECRET respectively. We should have also gotten your request token and request token secret saved in session variables $_SESSION[‘linkedin_oauth_token’] and $_SESSION[‘linkedin_oauth_token_secret’].
In Part I, we ended with redirecting user to LinkedIn authorization page, after user login and granted access, he/she will be brought back to the callback URI we specified earlier on. LinkedIn will pass in some parameters in the URI as GET parameters. This guide is about the basic steps to get the access token in the callback script.
Step 1 - Validate Parameters Passed Back by LinkedIn
If everything is correct and user granted our website the access, LinkedIn will redirect him/her back to the callback URI with some parameters. The 2 important parameters are $_GET[‘oauth_token’] and $_GET[‘oauth_verifier’]. The $_GET[‘oauth_token’] should be the same as the request token we used pass to LinkedIn when redirecting user to the authorization page.  We start by making sure these parameters are there and the token is the same.
if (empty($_GET['oauth_token']) || empty($_GET['oauth_verifier']) || $_GET['oauth_token']!=$_SESSION['linkedin_oauth_token']) {
  echo 'You must grant us access to proceed on.'; exit();
}
Step 2 - Prepare OAuth Parameters
The require parameters are listed in the code. It is quite similar with those used in Part I, but without oauth_callback and with oauth_token and oauth_verifier.
$params = array(
  'oauth_consumer_key'=>LINKEDIN_KEY,
  'oauth_nonce'=>sha1(microtime()),
  'oauth_signature_method'=>'HMAC-SHA1',
  'oauth_timestamp'=>time(),
  'oauth_token'=>$_GET['oauth_token'],
  'oauth_verifier'=>$_GET['oauth_verifier'],
  'oauth_version'=>'1.0'
);
Step 3 - Prepare The Base String
This is the similar as Part I. But with the URL replaced by the access token URL. Notice that $links[‘access_token’] was defined in Part I, you need to re-define it in the callback script if you are separating the Part I and Part II code as 2 files. I will show how to combine Part I and Part II codes in one script at the end.
// sort parameters according to ascending order of key
ksort($params);

// prepare URL-encoded query string
$q = array();
foreach ($params as $key=>$value) {
  $q[] = urlencode_oauth($key).'='.urlencode_oauth($value);
}
$q = implode('&',$q);

// generate the base string for signature
$parts = array(
  'POST',
  urlencode_oauth($links['access_token']),
  urlencode_oauth($q)
);
$base_string = implode('&',$parts);
Step 4 - Get The Signature
Again, this step is quite similar to Part I. In Part I, we don’t have any token secret, so we ended up using only the Consumer Key concatenate with an empty string. Now, we need to make use of the request token secret we obtained and saved in session in Part I.
$key = urlencode_oauth(LINKEDIN_SECRET) . '&' . urlencode_oauth($_SESSION['oauth_token_secret']);
$signature = base64_encode(hash_hmac('sha1',$base_string,$key,true));
Step 5 - Put the Signature into Parameters and Prepare Authorization Header
This step is again similar to Part I with the POST request URL change to /uas/oauth/accessToken.
$params['oauth_signature'] = $signature;
$str = array();
foreach ($params as $key=>$value) {
  $str[] = $key . '="'.urlencode_oauth($value).'"';
}
$str = implode(', ',$str);
$headers = array(
  'POST /uas/oauth/accessToken HTTP/1.1',
  'Host: api.linkedin.com',
  'Authorization: OAuth '.$str,
  'Content-Type: text/xml;charset=UTF-8',
  'Content-Length: 0',
  'Connection: close'
);
Step 6 - Send POST Request To LinkedIn
This step is exactly the same as Part I.
$fp = fsockopen("ssl://api.linkedin.com",443,$errno,$errstr,30);
if (!$fp) { echo 'Unable to connect to LinkedIn'; exit(); }
$out = implode("\r\n",$headers) . "\r\n\r\n";
fputs($fp,$out);

// getting LinkedIn server response
$res = '';
while (!feof($fp)) $res .= fgets($fp,4096);
fclose($fp);
If you do everything correctly, you should get a response like this. The response is quite similar to Part I as well.
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain
Content-Length: 236
Date: Sun, 07 Nov 2010 03:25:59 GMT

oauth_token=cd87b123-7623-8fdf-8k6k-73oef1e27a26&oauth_token_secret=1fe0345e-999d-431f-9ae6-9b55fe255c41&oauth_expires_in=0&oauth_authorization_expires_in=0
Step 7 - Parse the Response to Get The Access Token and Secret
$parts = explode("\n\n",str_replace("\r",'',$res);
$res_headers = explode("\n",$parts[0]);
if ($res_headers[0] != 'HTTP/1.1 200 OK') {
  echo 'Error getting access token and secret.'; exit();
}
parse_str($parts[1],$data);
if (empty($data['oauth_token'])) {
  echo 'Failed to get LinkedIn access token.'; exit();
}
Step 8 - Save the Request Token and Secret
At this point, you should have gotten the user access token and access token secret in $data[‘oauth_token’] and $data[‘oauth_token_secret’] respectively. You need to save them as you need to use them in the rest of LinkedIn API to perform protected request like getting user LinkedIn information, posting Shares on behave of user and etc. For this example, I will save it in session, you may in fact, saved it in MySQL database as LinkedIn access token and access token secret do not expire (based on the return above).
$_SESSION['linkedin_access_token'] = $data['oauth_token'];
$_SESSION['linkedin_access_token_secret'] = $data['oauth_token_secret'];

// unset the Request Token (not needed anymore)
unset($_SESSION['linkedin_oauth_token']);
unset($_SESSION['linkedin_oauth_token_secret']);
Combining Part I and Part II
So far, we have discussed Part I and Part II separately. You can combine Part I and Part II in a single script with some control logic.
// Defining all the constants, function and links here
// --- Step 1, 2 and 3 of Part I ---

// Use validation logic in Step 1 of Part II to decide if it
// it is a callback or direct access
if (empty($_GET['oauth_token']) || empty($_GET['oauth_verifier']) || $_GET['oauth_token']!=$_SESSION['linkedin_oauth_token']) {
  // Do the remaining Step 4 and onward of Part I to redirect user to LinkedIn authorization page
  // --- Step 4 and onward of Part I ---
}

// At this point you should have the Request Token in session already.
// Use the Request Token to obtain the Access Token
// --- Step 2 and onward of Part II ---
Important Observation in Part I and Part II
You should have noticed that Part I and Part II is quite similar. For Part I, we started without any token and token secret. So, we used Consumer Secret and empty token secret for signing the base string and did not pass any token to request for the temporary token known as Request Token from LinkedIn. For Part II, we have already gotten the Request Token. Thus, we used Consumer Secret and Request Token Secret to sign the base string. We passed in the request token when requesting for Access Token - the ‘ultimate’ token we wanted to get. Once you get the Access Token, the Request Token has no other use and can be deleted. In fact, for the rest of LinkedIn API, you will be doing similar things as in Part II. To access protected LinkedIn resources or perform action on behalf of user in LinkedIn, you will use Consumer Secret and Access Token Secret to sign the base string and pass in the Access Token when performing GET,POST or PUT request to LinkedIn.
I will write the Part III that showing examples of how to do some common requests like getting user information and posting Share. Follow me in Tumblr if you would like to get notify when it is up.

PHP: Step-by-step OAuth for Dummies based on LinkedIn API (Part I)

Part I: Redirecting User to LinkedIn

LinkedIn API is so far one of the most problematic API for developers who are trying to implement “connect” features.  The main reason is LinkedIn API is using XML for passing parameters for POST/PUT request while many standard OAuth libraries implementation are based on the more usual “query string” method.
I have tried very hard to find a good standard OAuth library for connecting to LinkedIn API, but guess what? It is so hard to find that I finally decided to dig to the basic of OAuth.  I started implementing based on LinkedIn OAuth Authentication Guide but apparently I stuck in creating a valid signature. I looked through many other guides and finally found out that LinkedIn guide actually missed out a few details. Firstly, it only mentioned about signing with consumer secret, but not with token secret.  Secondly, it didn’t really tell us what standard we should follow for url-encoding.  After struggling through many guides, I finally came out with my own step-by-step guide.
Step 1 - Get Your Consumer Key and Consumer Secret
Register your web application with LinkedIn at https://www.linkedin.com/secure/developer. You will get a Consumer Key (API Key) and a Consumer Secret code.  Lets start by this 2 lines of code:
define('LINKEDIN_KEY', 'YOUR_KEY');
define('LINKEDIN_SECRET', 'YOUR_SECRET'); 
Step 2 - Prepare The URL-Encoding Function Your Will Need
The URL encoding in OAuth follow the RFC-3896 standard. I did not really go through the standard, but the following is the function I get from most OAuth library
function urlencode_oauth($str) {
  return
    str_replace('+',' ',str_replace('%7E','~',rawurlencode($str)));
}
Step 3 - Prepare The Links
This is optional, but by keeping this organized, we can re-use the code for other web services.
$links = array(
 'request_token'=>'https://api.linkedin.com/uas/oauth/requestToken',
  'authorize'=>'https://www.linkedin.com/uas/oauth/authorize',
  'access_token'=>'https://api.linkedin.com/uas/oauth/accessToken'
);
Step 4 - Prepare OAuth Parameters
The require parameters are listed in the code. You will need to change the value of oauth_callback to your own callback URL.
$params = array(
  'oauth_callback'=>"http://www.example.com/linkedin_callback.php",
  'oauth_consumer_key'=>LINKEDIN_KEY,
  'oauth_nonce'=>sha1(microtime()),
  'oauth_signature_method'=>'HMAC-SHA1',
  'oauth_timestamp'=>time(),
  'oauth_version'=>'1.0'
);
Step 5 - Prepare The Base String
The base-string is a single line string that will be used for signing. It contains the following:
  1. The HTTP method in uppercase - GET, POST or PUT
  2. The request URL
  3. The query string sorted according to ascending order of key
These 3 items are url-encoded and combine into single line with ampersand (&) as seperator
// sort parameters according to ascending order of key
ksort($params);

// prepare URL-encoded query string
$q = array();
foreach ($params as $key=>$value) {
  $q[] = urlencode_oauth($key).'='.urlencode_oauth($value);
}
$q = implode('&',$q);

// generate the base string for signature
$parts = array(
  'POST',
  urlencode_oauth($links['request_token']),
  urlencode_oauth($q)
);
$base_string = implode('&',$parts);
Step 6 - Get The Signature
As a rule of thumb, the key for signature should be the URL-encoded Consumer Secret concatenate with token secret with ‘&’ as separator. At this stage, we don’t have any token secret, so it will be blank, but the ampersand (&) must still be there.
$key = urlencode_oauth(LINKEDIN_SECRET) . '&';
$signature = base64_encode(hash_hmac('sha1',$base_string,$key,true));
Step 7 - Put the Signature into Parameters and Prepare Authorization Header
$params['oauth_signature'] = $signature;
$str = array();
foreach ($params as $key=>$value) {
  $str[] = $key . '="'.urlencode_oauth($value).'"';
}
$str = implode(', ',$str);
$headers = array(
  'POST /uas/oauth/requestToken HTTP/1.1',
  'Host: api.linkedin.com',
  'Authorization: OAuth '.$str,
  'Content-Type: text/xml;charset=UTF-8',
  'Content-Length: 0',
  'Connection: close'
);
Step 8 - Send POST Request To LinkedIn
For this example, I use some basic functions - fsockopen(), fputs(), fgets(). You can implement this with other library like cURL.
$fp = fsockopen("ssl://api.linkedin.com",443,$errno,$errstr,30);
if (!$fp) { echo 'Unable to connect to LinkedIn'; exit(); }
$out = implode("\r\n",$headers) . "\r\n\r\n";
fputs($fp,$out);

// getting LinkedIn server response
$res = '';
while (!feof($fp)) $res .= fgets($fp,4096);
fclose($fp);
If you do everything correctly, you should get a response like this.
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain
Content-Length: 236
Date: Sun, 07 Nov 2010 03:25:59 GMT

oauth_token=cc652566-5243-835a-b12a-489224fc2bdb&oauth_token_secret=16e98de3-35ab-4508-9528-80a89d6137bc&oauth_callback_confirmed=true&xoauth_request_auth_url=https%3A%2F%2Fapi.linkedin.com%2Fuas%2Foauth%2Fauthorize&oauth_expires_in=599
Step 9 - Parse the Response to Get OAuth Token and Secret
$parts = explode("\n\n",str_replace("\r",'',$res));
$res_headers = explode("\n",$parts[0]);
if ($res_headers[0] != 'HTTP/1.1 200 OK') {
  echo 'Error getting OAuth token and secret.'; exit();
}
parse_str($parts[1],$data);
if (empty($data['oauth_token'])) {
  echo 'Failed to get LinkedIn request token.'; exit();
}
Step 10 - Save the Request Token and Secret
At this point, you should have your request token and request token secret in $data[‘oauth_token’] and $data[‘oauth_token_secret’] respectively. You need to save it as you need to use them in your callback script to get the access token in Part 2. For this example, I will save it in session.
$_SESSION['linkedin_oauth_token'] = $data['oauth_token'];
$_SESSION['linkedin_oauth_token_secret'] = $data['oauth_token_secret'];
Step 11 - Redirect your user to LinkedIn with the request token
header('Location: '.$links['authorize'].
  '?oauth_token='.urlencode($data['oauth_token']));
exit();
Your user should now see the LinkedIn Authorization page to grant access to your website to access his/her LinkedIn data. Once the user click authorized, LinkedIn will redirect him/her back to your callback URL, which ishttp://www.example.com/linkedin_callback.php in this example. LinkedIn will pass you some parameters so that you can proceed to Part 2 - Getting The Access Token.

Congrats! You have completed the first part of OAuth Authentication! With understanding the above steps, you should have no problem in implementing the rest of the things like getting the access token, and accessing LinkedIn API. I will be working on Part 2 - Getting The Access Token very soon to show that it is all very similar.
Update: I have posted Part II - Getting The Access Token.
 
-