Authenticating LinkedIn with OAuth 2.0 using CherryPy

Posted by on

LinkedIn is a popular social media platform for professionals. Using their API, we can fetch data from LinkedIn and use for a purpose like logging in using LinkedIn, fetching profile details etc.

In this code, we will look at how we can authenticate ourselves so that we can fetch the data from LinkedIn. For this purpose, LinkedIn relies on OAuth 2.0 protocol for granting access. Documentation for this can be found out here.

Experiment Setup:

  1. Language: Python3. Below code will not work as it is with Python2, as Python3 does not have urllib2, and all the required functions are in urlib only. Whereas Python2 has two different libraries with the different functions.
  2. IDE: Spyder
  3. Framework: CherryPy

We are using CherryPy because it is a minimalist framework with its own wsgi server. You can read more about CherryPy here. I have used CherryPy for many projects. One of the biggest advantages of learning CherryPy at the beginning is that things do not happen automagically. And you can actually learn Python more than you learn a particular framework.

If CherryPy is not installed in your Python distribution, you can install it using

>>pip install cherrypy

Create a python file in Spyder and type this basic code required for our application

import json
import urllib
import urllib.error
from urllib.request import Request, urlopen
import urllib.parse
import string
import random

import cherrypy
from cherrypy.lib.cptools import redirect


class LinkedInAuth(object):
    # Request Authorization Token
    
    # Callback function

# Turn on sessions in CherryPy. By default sessions are disabled in CherryPy
cherrypy.config.update({'tools.sessions.on': True})
# Start the server
cherrypy.quickstart(LinkedInAuth())

Steps

Let us go through the LinkedIn documentation for authenticating ourselves. (Link in the second paragraph).

  1. First of all, we need to create the LinkedIn application and configure it.
    1. Go to https://www.linkedin.com/secure/developer?newapp= and give your application some name. Provide some description, upload a logo. You can specify application use as ‘Other’. Since we are using CherryPy, by default it runs on http://127.0.0.1:8080. Provide this URL as website URL. Provide your email address and phone number.
    2. Once the application is created, you will be taken to the application page, where you will see Client ID and Client Secret. Keep both of these private.
    3. Under default application permissions, check r_basicprofile and r_emailaddress. Since we are interested in only fetching the data at the moment, we will not ask permission for company admin and sharing.
    4. Under authorised redirect URLs, fill http://127.0.0.1:8080/callback
  2. Now that we have created an application, we need to request an authorization code from LinkedIn. In our code under comment “Request Authorization Token” type following code
        @cherrypy.expose
        def index(self):
            # dictionary of request parameters
            request_params = {
                'response_type': 'code',
                'client_id': 'Your client id',
                'redirect_uri': 'http://127.0.0.1:8080/callback',
                'state': ''.join(random.sample(string.hexdigits, 18)),
                'scope': 'r_emailaddress r_basicprofile w_share'
            }
            # store state, a random string in session variable called
            # linkedin_auth_code
            cherrypy.session['linkedin_auth_code'] = request_params['state']
            # prepare url by encoding request parameters and appending it to url
            # and then redirect it to that url to get request token
            url = 'https://www.linkedin.com/oauth/v2/authorization?'
            raise cherrypy.HTTPRedirect(url + urllib.parse.urlencode(request_params))
    

    The definition of parameters sent to LinkedIn can be seen in the LinkedIn documentation.

    Once you will execute this code you will be redirected to a page which will look like this

    When you will click allow, you will be redirected to the callback URL. But we have not yet programmed as to what to do when we redirected to the callback.

  3. LinkedIn API returns state value which we had sent initially. This value needs to be compared with the one we had stored in the session so that we can verify that this is not a CSRF attack. Also, a code is returned. It also returns an error if any. Code for callback function goes like this. Write this function below comment “Callback function”
        @cherrypy.expose
        def callback(self, code='', state='', error='', error_description=''):
            # check if state value matches the one we had stored and sent in first
            # request. Also check if there is any error returned by LinkedIn
            if state == cherrypy.session['linkedin_auth_code'] and error == '':
                # dictionary of request parameters
                request_params = {
                    'grant_type': 'authorization_code',
                    'code': code,
                    'redirect_uri': 'http://127.0.0.1:8080/callback',
                    'client_id': 'Your client id',
                    'client_secret': 'Your client secret'
                }
                # url which needs to be accessed to get token
                url = 'https://www.linkedin.com/oauth/v2/accessToken'
                try:
                    # set headers
                    headers = {'Content-type': 'application/x-www-form-urlencoded'}
                    # encode headers and convert the url and parameters to a POST
                    # request                
                    request = Request(url, urllib.parse.urlencode(request_params).encode(), headers=headers)
                    # store the response received in the response variable. response
                    # contains authorization token
                    response = json.loads(urlopen(request).read().decode())
    
                    # we will use the authorization token received in exchange of 
                    # access token
                    
                    # url for POST request
                    url = 'https://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,location' \
                          ',positions,picture-url,public-profile-url,email-address)?format=json'
                    # set headers
                    headers = {'Authorization': 'Bearer %s' % response['access_token']}
                    # execute POST request
                    request = Request(url, headers=headers, method='GET')
                    # store response received in response variable and decode it
                    response = (urlopen(request).read().decode())
                    return response
                except urllib.error.HTTPError as e:
                    if e.code == 401:
                        redirect('/')
                    return str(e.code)
                except urllib.error.URLError as e:
                    return str(e.code)
            else:
                return 'Callback error. Error Description: %s' % error_description
    

    The definition of request parameters to get access token can be reviewed on the LinkedIn documentation.

  4. Now rerun the program, first of all, you will see a LinkedIn auth screen. Once you click Allow, you will be redirected to callback URL. Here our callback function checks for the authenticity of state and then exchanges authorization token for the access token and fetches your information. Finally, you will be able to see a JSON output with various details in your browser.

This is the first step that you need to carry out if you want to use LinkedIn API. And we have achieved this using CherryPy. But if you are comfortable with Flask or Django or any other Pythonic framework for that matter, you can execute this code with minor changes.

Complete Code

import json
import urllib
import urllib.error
from urllib.request import Request, urlopen
import urllib.parse
import string
import random

import cherrypy
from cherrypy.lib.cptools import redirect


class LinkedInAuth(object):
    # Request Authorization Token
    @cherrypy.expose
    def index(self):
        # dictionary of request parameters
        request_params = {
            'response_type': 'code',
            'client_id': 'Your client id',
            'redirect_uri': 'http://127.0.0.1:8080/callback',
            'state': ''.join(random.sample(string.hexdigits, 18)),
            'scope': 'r_emailaddress r_basicprofile w_share'
        }
        # store state, a random string in session variable called
        # linkedin_auth_code
        cherrypy.session['linkedin_auth_code'] = request_params['state']
        # prepare url by encoding request parameters and appending it to url
        # and then redirect it to that url to get request token
        url = 'https://www.linkedin.com/oauth/v2/authorization?'
        raise cherrypy.HTTPRedirect(url + urllib.parse.urlencode(request_params))

    # Callback function
    @cherrypy.expose
    def callback(self, code='', state='', error='', error_description=''):
        # check if state value matches the one we had stored and sent in first
        # request. Also check if there is any error returned by LinkedIn
        if state == cherrypy.session['linkedin_auth_code'] and error == '':
            # dictionary of request parameters
            request_params = {
                'grant_type': 'authorization_code',
                'code': code,
                'redirect_uri': 'http://127.0.0.1:8080/callback',
                'client_id': 'Your client id',
                'client_secret': 'Your client secret'
            }
            # url which needs to be accessed to get token
            url = 'https://www.linkedin.com/oauth/v2/accessToken'
            try:
                # set headers
                headers = {'Content-type': 'application/x-www-form-urlencoded'}
                # encode headers and convert the url and parameters to a POST
                # request                
                request = Request(url, urllib.parse.urlencode(request_params).encode(), headers=headers)
                # store the response received in the response variable. response
                # contains authorization token
                response = json.loads(urlopen(request).read().decode())

                # we will use the authorization token received in exchange of 
                # access token
                
                # url for POST request
                url = 'https://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,location' \
                      ',positions,picture-url,public-profile-url,email-address)?format=json'
                # set headers
                headers = {'Authorization': 'Bearer %s' % response['access_token']}
                # execute POST request
                request = Request(url, headers=headers, method='GET')
                # store response received in response variable and decode it
                response = (urlopen(request).read().decode())
                return response
            except urllib.error.HTTPError as e:
                if e.code == 401:
                    redirect('/')
                return str(e.code)
            except urllib.error.URLError as e:
                return str(e.code)
        else:
            return 'Callback error. Error Description: %s' % error_description
     
# Turn on sessions in CherryPy. By default sessions are disabled in CherryPy
cherrypy.config.update({'tools.sessions.on': True})
# Start the server
cherrypy.quickstart(LinkedInAuth())

Note:

I am no more writing regarding Python or programming on this blog, as I have shifted my focus from programming to WordPress and web development. If you are interested in WordPress, you can continue reading other articles on this blog.
Thanks and Cheers ????

>