bannerColor WARNING_YELLOW templateName stackForums

Exchange JSAPI Tokens for REST API OAuth Tokens

Intro

This document shows you how to "link" the REST API and JavaScript API together via our new token exchange flow. It's now possible for your app to implement "Sign-in with LinkedIn" via an easy JS API button ... and then you can pass the authorization token to your backend and make normal OAuth calls. Going forward, you can use both APIs simultaneously!

We hope this feature delivers the best of both worlds: easy, quick client-side programming with JavaScript combined with powerful, robust, background API calls from your server.

Quick requirements before you proceed:

  • SSL - your application must be able to accept inbound https requests.
  • OAuth Experience - we strongly recommend that you know how to make a normal LinkedIn OAuth'd REST API call before proceeding.

Background

When accessing the LinkedIn API, your code must supply an authorization token. Historically, LinkedIn has offered two types of tokens:

  • OAuth 1.0a - These tokens come from our normal OAuth flow and are used with our RESTful API. Typically, these tokens live forever (unless the granting user revokes them).
  • Client-side Bearer Tokens - These tokens are used by the JavaScript API so that your code can make calls directly from the user's web browser to LinkedIn (no backend required!). For security purposes, these client-side tokens are only valid for a short time (~30 minutes).

The short-lived nature of bearer tokens is not a problem for client-side applications in the browser; but frequently, developers are building applications which need both browser- and server- based API calls.  Historically, there has been no way for a single application to live in both worlds - it was either a full OAuth 1.0a flow with server-side integration or a clientside app based on our JavaScript API (the tokens could not mix). This is now fixed! We are now announcing a new Token Exchange solution which lets a developer use our JavaScript API and backend REST API together!

Token Exchange Flow

Programmatically, the token exchange flow is simple:

  1. An application implements "Sign-in with LinkedIn" via an easy JavaScript API button. No crazy OAuth dance is required. Once a user completes the sign-in flow, the browser now has a JavaScript API bearer token.
  2. The application securely passes the JSAPI token from the user's browser to the application's server/backend.
  3. The backend code makes a call to LinkedIn to exchange the short-lived token for a 60-day, OAuth 1.0a token. The application confirms that this is secure and safe by signing the request with their API secret (normal OAuth, nothing propietary).

To implement this flow, we built two pieces of technology:


A Secure Credentials Cookie - In order to pass (securely!) a user's bearer token from the browser to your server, we have opted to use a secure cookie. At your request, our framework will write a secure, session-only cookie on your domain with the authorization token (and some additional security info). Then, any subsequent https page requests to your domain will communicate the token to your backend.

An Exchange API - Once you have the bearer token on your server, you call a new LinkedIn API for exchanging it. We have simply built a REST API that takes the bearer token as parameter and returns a 60-day, normal OAuth 1.0a token as response. Calling this API is the same as making any LinkedIn REST API call, nothing special. Please note, calling this API requires that you identify yourself with your API secret.

Below, we have provided an end-to-end walkthrough for the token exchange flow. If you're not familiar with our REST API and OAuth, we urge you to read our documentation on OAuth first and make a successful LinkedIn REST API call before continuing.

We use JavaScript and PHP for our examples to illustrate each step. You can download the full sample code in PHP at the bottom of the document. If you write code in another language, please post a link below and we will add a link to it here. We have specifically tested this API with a variety of off-the-shelf OAuth libraries - if you find any special challenges or difficulties using this API, please let us know asap.

Walkthrough - Exchanging a Token in Four Steps

Programmatically, the token exchange divides into four high-level parts:

1. Passing the JS API bearer token to your server using a secure credentials cookie.

2. On your server, reading the cookie and validating its signature to ensure legitimacy.

3. Calling the Exchange API to perform the exchange and receive the OAuth 1.0a tokens for the LinkedIn REST API.

4. Making REST API calls with the retrieved tokens.

Let's attack them in order.  If you want to see some existing code, you can use the PHP code attached to this document.  In order to get the code to work correctly you need the following setup:

  • HTTPS server running (you can use a self-signed certificate for testing this process)
  • Your API key pointing correctly to the domain of the server
  • You must have PEAR, Pecl and oauth installed (and included in php.ini) for your PHP installation

1. Passing the JS API OAuth 2.0 token to your server using a secure cookie.

Configure the JS API to write a secure cookie on your application domain by adding credentials_cookie: true to the framework initialization script tag:

<script src="http://platform.linkedin.com/in.js">
api_key: YOUR_API_KEY
authorize: true
credentials_cookie: true
</script>

After that's set, the browser will automatically pass along the cookie to your server on all subsequent HTTPS requests (in this session). Of course, this happens only when a user has authorized your app. If the viewing user has never authorized your app (i.e. never "Signed in with LinkedIn), then your app will not receive any cookie.

For security and privacy reasons, the cookie is only available over https. This requires an SSL certificate and a web server configured to accept HTTPS calls. Unfortunately, since the process varies so widely based on servers and operating systems, you will need to research the best way to do this on your own. If you are using Apache, you can refer to their SSL FAQ for instructions on creating a self-signed certificate and installing it on your server. If you're doing local development on a Mac, there are instructions for creating an https server here. If you make a vanilla, non-SLL HTTP request, no cookie is sent.

An easy way to make an HTTPS request from your page is with an AJAX call. Be sure to reference the page using a fully qualified domain.

Here's a short example that uses jQuery inside of the onAuth callback function:


<html>
<head>
<title>LinkedIn JavaScript API Token Exchange</title>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>

<script src="http://platform.linkedin.com/in.js">
api_key: YOUR_API_KEY
authorize: true
credentials_cookie: true
</script>

<script>
// Once we have an authorization, pass back the token
function onLinkedInAuth() {
// this must be the same domain as the application, where we write the cookie
$.post('https://www.example.org/token-exchange.html');
}
</script>
</head>
<body>
<script type="in/login" data-onAuth="onLinkedInAuth"></script>
</body>
</html>

Since the JS API uses a secure cookie, you don't need to worry about passing any token information yourself; it comes included as part of the browser's request. Your only requirement is to read the cookie.

If you prefer not to use AJAX, you could also redirect the member to an HTTPS page automatically. But before you do this, please wait for the "onAuth" event to fire - we write the cookie locally from JavaScript and you are not guaranteed that the cookie is available until the "onAuth" fires. You must also set the "authorize" parameter to "true". Otherwise, when you do a redirect, the cookie will not be passed because you did not ask us to preserve the signed in state of the member.

<script>
function onLinkedInAuth() {
  location.href = "https://www.example.org/handler-which-reads-li-auth-cookie.php"
}
</script>

(Elsewhere, be sure to register this function as an event handler for the auth event using the IN.Event API.)

2. Reading the cookie and validating its signature to ensure legitimacy.

Whether you use AJAX or not, you can use the same code to parse and process the cookie.

The cookie is named linkedin_oauth_API_KEY, where API_KEY is your application's LinkedIn API key. (This is also known as a "consumer_key" in OAuth.)

Its value is a JSON object, which looks like this:


{
"signature_method":"HMAC-SHA1",
"signature_order": ["access_token", "member_id"],
"access_token":"AD2dpVe1tOclAsNYsCri4nOatfstw7ZnMzWP",
"signature":"73f948524c6d1c07b5c554f6fc62d824eac68fee",
"member_id":"vvUNSej47H"
"signature_version": 1
}

There are many fields here! The fields of main interest are access_token and member_id. The access_token field is is the JS API bearer token that we are securely passing from the user's browser to your backend. The member_id field is the unique identifier assigned to this particular user, you can use it to link this member to their account in your database.

The rest of the fields are for security purposes (a signature to help you verify authenticity) and for programmatic convenience (a version number in case we update the protocol going forward).

Begin by extracting and converting the JSON cookie to a native object or hash:


// extract data from cookie stored in json
$consumer_key = 'YOUR_API_KEY';
$cookie_name = "linkedin_oauth_${consumer_key}";
$credentials_json = $_COOKIE[$cookie_name]; // where PHP stories cookies
$credentials = json_decode($credentials_json);

As mentioned, the access_token field contains the JS API bearer token which you will use for the actual exchange API call later. Before performing the exchange, however, you should be sure that this is a valid cookie written to your domain by LinkedIn (more information on why you should care about this validation is included below in the FAQ). To validate, we provide you with other details that allow you to calculate a cookie signature using your API key secret. Compare your generated signature with the one in the cookie's signature field; if they match, you know the cookie is legit and from LinkedIn.

The signature base is calculated by concatenating the values of the fields listed in the signature_order field in the order they appear. In this case, it's access_token followed by member_id, or "AD2dpVe1tOclAsNYsCri4nOatfstw7ZnMzWPvvUNSej47H".

After you construct the signature base, use the encryption algorithm specified in the signature_method to calculate the signature using your API secret. For now, the only value we're using is Base64 encoded HMAC-SHA1, the same algorithm used by OAuth. (We separated this out for compatibility just in case.) The value for the HMAC-SHA1 key is the value of the "API secret" from your Application Details page. (This is called the consumer_secret in OAuth.)

Note: For future compatibility, we provide a version field in the cookie. Today, there is only one version, 1. That may change in the future, but it's best to check now.

When you're done, you have a signature to compare to the one from the cookie:

$consumer_secret = 'YOUR_SECRET_KEY';

// validate signature
if ($credentials->signature_version == 1) {
    if ($credentials->signature_order && is_array($credentials->signature_order)) {
        $base_string = '';
        // build base string from values ordered by signature_order
        foreach ($credentials->signature_order as $key) {
            if (isset($credentials->$key)) {
                $base_string .= $credentials->$key;
            } else {
                print "missing signature parameter: $key";
            }
        }
        // hex encode an HMAC-SHA1 string
        $signature =  base64_encode(hash_hmac('sha1', $base_string, $consumer_secret, true));
        // check if our signature matches the cookie's
        if ($signature == $credentials->signature) {
            print "signature validation succeeded";
        } else {
            print "signature validation failed";    
        }
    } else {
        print "signature order missing";
    }
} else {
    print "unknown cookie version";
}

3. Making an API call to receive the OAuth 1.0a tokens for the REST API.

Now that you have a legitimate access token, make a secure HTTPS POST to our Access Token URL, https://api.linkedin.com/uas/oauth/accessToken, to retrieve it.

You need to pass four values as query parameters:

  1. oauth_consumer_key, to identify yourself
  2. xoauth_oauth2_access_token parameter, set to the value of the access_token field in the cookie.
  3. signature_method set to HMAC-SHA1
  4. signature, calculated as described in the OAuth 1.0a spec

The easiest way to accomplish this is by using an off-the-shelf OAuth library (because it will handle 1, 3, and 4 automatically). You only need to pass 2 as a parameter value. For example, using the PHP 5 pecl/oauth library:


// configuration settings
$consumer_key = 'YOUR_API_KEY';
$consumer_secret = 'YOUR_SECRET_KEY';
$access_token_url = 'https://api.linkedin.com/uas/oauth/accessToken';

// init the client
$oauth = new OAuth($consumer_key, $consumer_secret);
$access_token = $credentials->access_token;
// swap 2.0 token for 1.0a token and secret
$oauth->fetch($access_token_url, array('xoauth_oauth2_access_token' => $access_token), OAUTH_HTTP_METHOD_POST);
// parse the query string received in the response
parse_str($oauth->getLastResponse(), $response);

// Debug information
print "OAuth 1.O Access Token = " . $response['oauth_token'] . "\n";
print "OAuth 1.O Access Token Secret = " . $response['oauth_token_secret'] . "\n";

4. Making REST API calls with the retrieved tokens

Now you can make REST API calls from your backend as if you had completed the regular OAuth 1.0a flow. This token has all the lifecycle semantics as our normal OAuth 1.0a flow. Going forward, you application can use both types of tokens simultaneously - JS API tokens for quick, direct-to-browser API calls and the OAuth 1.0a tokens for background calls. Once you've made the exchange once (per user), you never need to worry about it again - you can make background/offline calls from your server and normal JS API calls whenever the user is on your site (when a valid browser session is available).

Use your existing OAuth client, updating the credentials to identify the member:


// set the new token values for 1.0a requests
$oauth->setToken($response['oauth_token'],$response['oauth_token_secret']);

Then make a RESTful LinkedIn API call via OAuth 1.0a:


// go to town, fetch the user's profile
$url = 'http://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline)';
$oauth->fetch($url, array(), OAUTH_HTTP_METHOD_GET, array('x-li-format' => 'json')); // JSON!
$profile = json_decode($oauth->getLastResponse());
print "$profile->firstName $profile->lastName is $profile->headline.";

This REST API code above is equivalent to the following JS API code:


IN.API.Profile("me")
.fields('firstName', 'lastName', 'headline')
.result( function(r) {
var user = r.values[0];
var msg = user.firstName + " " + user.lastName + " is " + user.headline + ".";
alert(msg);
}
);

Congratulations! You have successfully exchanged your JS API token for a REST API token. You can now make any of our REST API calls from your backend environment. The lifetime of this new token is 60-days, so you can make background calls whenever you want - you are no longer bound by the short lifetime of the bearer tokens. Enjoy!

FAQ

Q: Do I need to call the exchange API every time a user visits my site?

A: No, the exchange only needs to happen one time (typically early in the user's account lifecycle). Once you have the 60-day OAuth 1.0a token on your backend, you can use that for OAuth calls to our REST APIs. Subsequent usage of the JS API, when the user visits your site again, will work with new client-side bearer tokens ... but you don't need to worry about re-exchanging. Of course, if the user revokes your application's authorization grant (and thus all your tokens become invalid), then you must do everything again.

Q: Why bother with signature validation? What's the point?

A: The main purpose of the credentials_cookie feature is to communicate, securely, two pieces of information from the user's browser to your application: an access_token and LinkedIn member_id for that user.

Practically speaking, there is little point to validating the access_token - if someone attempts to spoof an access token, it will be invalid and we will not exhange it. So it's useless.

However, it is very important to validate the member_id field. This is critical! If your application relies on the LinkedIn JavaScript API for authentication, you need a way to verify that the incoming cookie identifies the correct user. In other words, we must prevent against spoofing attacks where a malicious user provides someone else's member_id as his or her own. By including the member_id in the signature_base (and computing the signature based on our shared API secret), we allow you to verify that the member_id has not been spoofed.

We strongly encourage you to always perform the signature validation.

Q: What's a typical scenario for using this functionality?

A: Let's say that you build a search application like http://instant.linkedinlabs.com. This is a rich, responsive application powered by the LinkedIn JavaScript API - all data and functionality flows smoothly between the user's browser and LinkedIn (you don't need a backend at all!). However, as a premium feature for your application, you want to email paying users once a week with search updates. To perform these searches, you need offline access. The JS API does not offer offline access, but by using the token Exchange API (described in this document) you can obtain an offline token for making OAuth 1.0a REST calls whenever necessary.