OAuth2 and the LinkedIn API – A WordPress CV/Résumé plugin

Jun 17, 2013
HTML PHP
By

Continuing on from last weeks introduction to OAuth 2.0 using the Twitter API, this week we’re going to focus on an alternate implementation of OAuth 2.0 used in the LinkedIn API. This implementation requires that you manually authorize the application before you can get a token.

This implementation is a more complex as it requires you to be re-directed to the authentication server, which then re-directs you back to your own site with the approved token. This allows LinkedIn to add additional permissions around certain content. You can authorize an application to have access to your full profile, or just your public profile.

You’ll need to register for a developer account and create a new application. You can do this here. Once you’re signed up, simply click on “Add New Application” and fill out the form.

register-app

Getting Started

I’ll be using almost an identical class layout as the last few weeks, so I’m going to skip the setup and get going in the core OAuth and LinkedIn code. Feel free to read over either my Twitter API or WordPress Admin Page tutorials

This template registers the required menu item, admin page and queues up the css for use in the shortcode.

class LinkedIn_Example {

    public function __construct() {
        add_shortcode('linkedin', array($this, 'render_shortcode'));
        add_action('admin_menu', array($this, 'add_linkedin_admin_menu'));
        add_action('wp_footer', array($this, 'enqueue_linkedin_style'));        
    }
    
    public function add_linkedin_admin_menu() {      
        add_options_page('LinkedIn Options', 'LinkedIn', 'manage_options', 'linkedin.php', array($this, 'add_linkedin_admin_page'));
        add_action( 'admin_print_scripts', array($this, 'enqueue_linkedin_style'));
    }  
    
    public function add_linkedin_admin_page() {
        if ( !current_user_can( 'manage_options' ) )  {
            wp_die( __( 'You do not have sufficient permissions to access this page.' ) );
        }
        
        if ( $_SERVER["REQUEST_METHOD"] == "POST" ){            
            update_option( 'LINKEDIN_API_KEY', $_POST['api_key'] );
            update_option( 'LINKEDIN_API_SECRET_KEY', $_POST['api_secret_key'] );
            update_option( 'LINKEDIN_USER_ID', $_POST['user_id'] );            
        }
        
        ?>
        <div class="linkedin-admin-options">
            <h1>LinkedIn Options</h1>
            <form name="options" method="POST" action="<?php echo $_SERVER['REQUEST_URI']; ?>">
                <label for="user_id">LinkedIn User ID<span class="required">(*)</span>: </label>
                <input type="text" name="user_id" value="<?php echo get_option( 'LINKEDIN_USER_ID', '' ); ?>" size="70">
                <br />
                <label for="api_key">API Key<span class="required">(*)</span>: </label>
                <input type="text" name="api_key" value="<?php echo get_option( 'LINKEDIN_API_KEY', '' ); ?>" size="70">
                <br />
                <label for="api_secret_key">Consumer Secret<span class="required">(*)</span>: </label>
                <input type="text" name="api_secret_key" value="<?php echo get_option( 'LINKEDIN_API_SECRET_KEY', '' ); ?>" size="70">    
                <br />                
                <label for="bearer_token">Authentication Token: </label>
                <input type="text" disabled value="<?php echo get_option( 'LINKEDIN_AUTHENTICATION_TOKEN', '' ); ?>" size="70">
                <br />
                <input class="button-primary" type="submit" name="save" />
                                
                <br/>
                <small>You can sign up for a API key <a href="https://developer.linkedin.com/" target="_blank">here</a></small>                
            </form>
            <br />
            <?php echo do_shortcode('[linkedin]'); ?>
        </div>
        <?php
    }
    
    public function render_shortcode($atts) {        
    }
    
    public function enqueue_linkedin_style() {
        $token = get_option( 'LINKEDIN_AUTHENTICATION_TOKEN' );
        $user_id = get_option( 'LINKEDIN_USER_ID' );
        
        if($token && $user_id) {
             wp_register_style( 'linkedin-style', get_template_directory_uri() . '/linkedin.css');

             wp_enqueue_style( 'linkedin-style' );
        }
    }
}

$linkedIn = new LinkedIn_Example();

As you can see there are 2 pieces of information we need to store this time. We need the API key and secret for the LinkedIn application that you just created above. Once you have the keys, we can attempt to authenticate the application. To do this we’ll first need to add a link to our form for use to click on.

app-keys

Authentication

Generating an Authorization Code

The initial URL requires a some additional values along with the API keys. We first have to specify the response_type, since LinkedIn required the authentication flow this must be set to “code”. We also have to specify our client_id, this is the API key from above. Next up we have to specify the scope or level of permissions we are requesting, for this demo I’ll be using r_fullprofile. We also need to specify a random base64 code for security purposes. This is a one time use code to help mitigate CSRF vulnerabilities. I’m just using the time function and then encoding it. Finally we need our redirect URI, this is the url to our linkedin.php file that will accept the final token and store it for us. This block fits just under the submit button in add_linkedin_admin_page

<?php
            
$state = base64_encode(time());
$redirect = get_template_directory_uri() . '/linkedin.php';                            
$api_key = get_option( 'LINKEDIN_API_KEY' );
$api_secret = get_option( 'LINKEDIN_API_SECRET_KEY' );
$token = get_option( 'LINKEDIN_AUTHENTICATION_TOKEN' );

if($api_key && $api_secret && !$token) {
    $api_url = "https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=$api_key&scope=r_fullprofile&state=$state&redirect_uri=$redirect";
    ?>
    <a class="button-primary" type="button" href="<?php echo $api_url; ?>">Authenticate</a>
    <?php
}

?>

Now when you click in the “Authenticate” button, you will be re-directed to LinkedIn and asked to confirm. (Don’t do this just yet, as we still have to add the logic to actually save the token!)

linkedin-auth

Once you’ve confirmed you wish to authenticate the application, you will be redirected to the linkedin.php file. We now have to make sure that when the linkedin.php file is requested, that it behaves appropriately and makes the final token request and saves it.

Requesting an Access Token

In our constructor, we’re going to add a conditional section if the REQUEST_METHOD is get and the ‘code’ parameter is set. We’re also going to have to require some WordPress files so we have access to some basic functions. Since this file exists in our theme folder the require_once lines look like this

public function __construct() {
    if ( $_SERVER["REQUEST_METHOD"] == "GET" && isset($_GET['code'])){
        
        require_once('../../../wp-config.php');
        require_once(ABSPATH . 'wp-includes/plugin.php');
        require_once(ABSPATH . 'wp-includes/pluggable.php');
        require_once(ABSPATH . 'wp-includes/general-template.php');
        
    }
    add_shortcode('linkedin', array($this, 'render_shortcode'));
    add_action('admin_menu', array($this, 'add_linkedin_admin_menu'));
    add_action('wp_footer', array($this, 'enqueue_linkedin_style'));        
}

Now that we have access to some basic WordPress functions, we can get our current URI using get_template_directory_uri and our 2 keys using get_option. We then build up our parameter array for the final wp_remote_post. The redirect url here, needs to be the same one we specified before. If it’s not the same, the request will fail. This is what the updated constructor method should look like.

$redirect = get_template_directory_uri() . '/linkedin.php';                            
$api_key = get_option( 'LINKEDIN_API_KEY' );
$api_secret = get_option( 'LINKEDIN_API_SECRET_KEY' );            

$args = array(
    'method' => 'POST',
    'httpversion' => '1.1',
    'blocking' => true,
    'body' => array( 
        'grant_type' => 'authorization_code',
        'code' => $_GET['code'],
        'redirect_uri' => $redirect,
        'client_id' => $api_key,
        'client_secret' => $api_secret
    )
);

add_filter('https_ssl_verify', '__return_false');
$response = wp_remote_post( 'https://www.linkedin.com/uas/oauth2/accessToken', $args );

Now all we need to do is parse the response body, store our new token and re-direct ourselves back to the admin settings page.

public function __construct() {
    if ( $_SERVER["REQUEST_METHOD"] == "GET" && isset($_GET['code'])){
        
        require_once('../../../wp-config.php');
        require_once(ABSPATH . 'wp-includes/plugin.php');
        require_once(ABSPATH . 'wp-includes/pluggable.php');
        require_once(ABSPATH . 'wp-includes/general-template.php');
        
        $redirect = get_template_directory_uri() . '/linkedin.php';                            
        $api_key = get_option( 'LINKEDIN_API_KEY' );
        $api_secret = get_option( 'LINKEDIN_API_SECRET_KEY' );            

        $args = array(
            'method' => 'POST',
            'httpversion' => '1.1',
            'blocking' => true,
            'body' => array( 
                'grant_type' => 'authorization_code',
                'code' => $_GET['code'],
                'redirect_uri' => $redirect,
                'client_id' => $api_key,
                'client_secret' => $api_secret
            )
        );

        add_filter('https_ssl_verify', '__return_false');
        $response = wp_remote_post( 'https://www.linkedin.com/uas/oauth2/accessToken', $args );
            
        $keys = json_decode($response['body']);
        
        if($keys) {
            update_option( 'LINKEDIN_AUTHENTICATION_TOKEN', $keys->{'access_token'} );
        }            
        wp_redirect( get_bloginfo( 'url' ) . '/wp-admin/options-general.php?page=linkedin.php' ); 
        exit; 
    }
    
    add_shortcode('linkedin', array($this, 'render_shortcode'));
    add_action('admin_menu', array($this, 'add_linkedin_admin_menu'));
    add_action('wp_footer', array($this, 'enqueue_linkedin_style'));        
}

Getting Our LinkedIn Profile

Now that we have our authentication token, we can populate our render_shortcode method. The LinkedIn API is very well documented and is pretty easy to use. You simply specify the API URL and append the profile fields you require along with our authentication token

add_filter('https_ssl_verify', '__return_false');
$api_url = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,industry,summary,positions,picture-url,skills,languages,educations,recommendations-received)?oauth2_access_token=$token&format=json";

$response = wp_remote_get( $api_url );

$json = json_decode( $response['body'] );

Requesting all of this data in one go results in quiet a large JSON result, but it’s very well constructed so it’s easy to parse. Since this shortcode is going to be a full CV, I’m going to break it down into sections. At the top I’ll have some quick links to jump down to each section.

Shortcuts

$return .= '<section class="shortcuts">';
$return .= '<h2><a href="#shortcuts">Quick links</a></h2>';
$return .= '<ul>';
$return .= '<li><a href="#skills">Skills</a></li>';
$return .= '<li><a href="#summary">Summary</a></li>';
$return .= '<li><a href="#positions">Positions</a></li>';
$return .= '<li><a href="#recommendations">Recommendations</a></li>';
$return .= '</ul>';
$return .= '</section>';

Profile Details

This is where I’ll display my full name and my ‘headline’

$return .= '<section class="about">';
$return .= '<h2>' . $json->{'firstName'} . ' ' . $json->{'lastName'} . '</h2>';
$return .= '<p>' . $json->{'headline'} . '</p>';
$return .= '</section>';

Skills

This will be just a CSV formatted list of my skills. The skills object contains the total count of skills and the actual collection of skills in a values object. This is a repeated pattern within this JSON for anything that is a collection. Another thing to note is that the large blocks of text are served as just that, they are not HTML encoded blocks. I have chosen to display them in <pre%gt; tags, but you could replace ‘\n’ characters with <br /%gt; tags if you wanted.

$skills = $json->{'skills'}->{'values'};
$first = true;
$return .= '<section class="skills">';
$return .= '<h2><a href="#" name="skills">Skills</a></h2>';
$return .= '<pre style="font-size: smaller;">';
foreach($skills as $i => $skill) {
    $return .= ( $first == false ? ', ' : '') . $skill->{'skill'}->{'name'};
    $first = false;
}
$return .= '</pre>';

Summary

The large block of ‘about me’ text

$return .= '<h2><a href="#" name="summary">Summary</a></h2>';
$return .= '<pre>' . $json->{'summary'} . '</pre>';
$return .= '</section>';

Jobs

This will be a list of my job history, once again the actual jobs are under the ‘values’ collection.

$jobs = $json->{'positions'}->{'values'};
$return .= '<section class="positions">';
$return .= '<h2><a href="#" name="positions">Positions - ' . $json->{'industry'} . '</a></h2>';
$return .= '<p>';

foreach($jobs as $i => $job) {
    $return .= '<h2>' . $job->{'title'} . '</h2>';
    $return .= '<h3>' . $job->{'company'}->{'name'};
    $return .= ' ( ' . $job->{'startDate'}->{'year'} . ' - ';
    if($job->{'isCurrent'} == "true"){
        $return .= 'Current';
    } else {
        $return .= $job->{'endDate'}->{'year'};
    }
    $return .= ' )</h3>';
    $return .= '<pre>' . $job->{'summary'} . '</pre>';

}

$return .= '</p>';
$return .= '</section>';

Recommendation

Finally I’ll list my recommendations from various employers or people I’ve worked with. I’ve chosen to display this in the <blockquote> tag.


$return .= '</p>';
$return .= '</section>';

$recommendations = $json->{'recommendationsReceived'}->{'values'};
$return .= '<section class="recommendations">';
$return .= '<h2><a href="#" name="recommendations">Recommendations</a></h2>';
foreach($recommendations as $i => $recommendation) {
    $recommendedBy = $recommendation->{'recommender'};
    $return .= '<h3>' . $recommendedBy->{'firstName'} . ' ' . $recommendedBy->{'lastName'} . '</h3>';
    $return .= '<blockquote>';
    $return .= $recommendation->{'recommendationText'};                
    $return .= '</blockquote>';                
}
$return .= '</section>';

Finishing Up

Now using the [linkedin] shortcode I can display my entire CV with in my site. I could also quite easily add another shortcode or widget to display one or more sections, such as the recommendations. I could then have them appear in the sidebar, or as parts of another post.

shortcode

As always, the full source code is available below. If you have any questions, please feel free to comment

Author: Jonathan Schnittger
A battle hardened software developer with a mixed and colorful background, who can't come up with a decent author bio
  • http://www.friv4game.net/ Friv 4

    thanks for post

  • http://www.fri-v.com/ Friv

    Thanks for this post.

  • http://www.minecraftgames.co/ Minecraft Games

    These are the necessary information and are given a very detailed, specific. thanks for sharing it.

  • Salman

    Can we display fields of visitor’s profile ? is there any way to add that functionalty?

  • reggiedog

    Can you post a screen shot of what the user’s profile page looks like after they sign in?

  • pharzan

    do you have such a tutorial for the google API?