Adding LinkedIn OAuth to web applications (without external libraries)
Tags: react, programming, webdev, javascript
Published: 2021-10-16 07:53:20


## Introduction
A recent project got me into **LinkedIn Oauth**, And boy it was a headache at first! And today i'll try to save you from that headache. So, lets get started!

## Setup
We'll be <u>needing a linkedin app</u> which would represent our web app for authentication to the user (create the linkedin app from [here](https://www.linkedin.com/developers/apps/new)). We also need a page for our app... (I know, this sh*t takes ages 😑)
![linkedin auth page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hkp66o2fwatr4nzem12y.png)

After creating a page with minimal details and pasting the page url in the input box, we are ALMOST done creating the app
![linkedin auth app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kazjx2fq4phiokt6upaq.png)

Now if we see our app on the url: https://www.linkedin.com/developers/apps, it should contain a section named **"Auth"**
![linkedin app auth section](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x64wx7skaz2c45b04wgt.png)

Going in the **Auth section**, we will be shown with some **INTERESTING** stuff. Here, we need to provide a **redirect uri** to the auth app.
![linkedin auth setup almost complete](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/card1h5728rity60a8uu.png)

*Note: <u>Redirect uri is the absolute url path of the part of your web app on which you want linkedin to redirect to after authentication</u>.

Here, i am hosting the web app on my local machine so i am giving the url `http://localhost:3000`, you may give any other route or url.
<u>I suggest to store the redirect uri in an environment variable in your app</u> as **we will be requiring it quite often** and also <u>env variables are suggested way to organise fundamental constant variables which allow us to use them in different environments with different values</u> (i.e production & development).

Now, coming to the final setup step. We haven't given any scopes/permissions for oauth to our app as we can see here in the **Auth tab**.
![linkedin oauth permissions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g6io14xr0jfvoelbcuf6.png)

To give permissions to our app, we need to go in **Products tab > Sign In with LinkedIn** and click on **Select > Add Product**. <u>LinkedIn will review our app and after a few moments, the product should be added to it</u> (this sh*t takes ages 😑). Once complete, it will be reflected in the **"Auth"** tab
![linkedin auth scopes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fnxvs7i69jcwggxxz0cy.png)
Phew! that was a lot of setup! lets move on to the fun part ;)

## Authentication Flow
LinkedIn auth has a 3-step authentication process:
1. Get the authorization `code` (done on frontend)
2. Exchange the code to get an `access_token` (requires backend)
3. Exchange the `access_token` to get user's details (email, name etc.) (requires backend)

### What I mean by requiring a backend?
<u>Responses from the requests to be made in the step 2 & 3 don't have an `Access-Control-Allow-Origin` header</u>. Which means that we can't read the data sent back in response by linkedin's servers **as the browser blocks these type of requests**, you can see more on this header [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).
_Thus, we need something which is not running on browser_.

## Code
### Step 1 (Fetching the `authorization_code`)
To get the authorization code, we need to redirect the user to this url with the following parameters:
![linkedin client redirect url](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/92b24tjxa4ty54mafvq6.png)

(the `state` parameter is not required and neither we will be using it)
Your final url to redirect user on, should look like this:
```javascript
`https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${process.env.REACT_APP_CLIENTID}&redirect_uri=${process.env.REACT_APP_REDIRECT_URI}&scope=r_liteprofile,r_emailaddress`
```

Now, lets move on to the frontend where we will be fetching the `authorization_code`, i am using React here but you could use pure vanillaJS too.
```javascript
// App.js
export default function App() {

  const linkedinRedirectUrl = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${process.env.REACT_APP_CLIENTID}&redirect_uri=${process.env.REACT_APP_REDIRECT_URI}&scope=r_liteprofile,r_emailaddress`

  const handleLinkedinLogin = () => {
    window.location.href = linkedinRedirectUrl
  }

  return (
    <div className="App">
      <button onClick={handleLinkedinLogin}>
        Login with LinkedIn
      </button>
    </div>
  );
}
```

Try clicking the button...
![it works](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/59amwykdvptxudxoc633.png)

**IT WORKS!**

Clicking on **Allow** will redirect to home page of our app, but in the address bar there's something different!
![linkedin auth code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/owuyfp7t02br0b2s9ow5.png)

<u>Thats THE CODE!</u>

### Step 2 (Retrieving the `authorization_code`)
Now, in this step <u>we must retrieve the code after the redirection</u> so that we could perform operations on it. So i have written a `useEffect` to retrieve the `code` if it is present in address bar on every page load.
```javascript
// App.js
import { useEffect } from 'react'

export default function App() {

  const linkedinRedirectUrl = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${process.env.REACT_APP_CLIENTID}&redirect_uri=${process.env.REACT_APP_REDIRECT_URI}&scope=r_liteprofile,r_emailaddress`

  useEffect(() => {
    let windowUrl = window.location.href
    if (windowUrl.includes('code=')) {
      let codeMatch = windowUrl.match(/code=([a-zA-Z0-9_\-]+)/)
      // And to those who hate regex...
      // YES I used regex here! B*TCHES!
      // Anyway, I prefer learning it, quite POWERFUL as shown
      
    }

  }, [])

  const handleLinkedinLogin = () => {
    window.location.href = linkedinRedirectUrl
  }

  return (
    <div className="App">
      <button onClick={handleLinkedinLogin}>
        Login with LinkedIn
      </button>
    </div>
  );
}
```
About that regex i used, <u>it just means to pickup a group of characters which are lowercase alphabets (a-z), uppercase alphabets (A-Z), underscores & hyphens</u>. Reloading the page now extracts the `code`.
Now we need a backend to request the `access_token` exchanging this `code` and request user info then & there with granted `access_token`. Lets build that!

### Step 3 (fetching the user_info with `access_token`)
Here's the <u>backend code for the endpoint which will fetch the `access_token` & user info</u>.
```javascript
require('dotenv').config()
const cors = require('cors')
const axios = require('axios')

const app = require('express')()
app.use(cors())

app.get('/user', async (req, res) => {
    try {
        const code = req.headers.auth_code
        if (!code) throw new Error('No code provided')

        // This request gets access_token
        let accessTokenResponse = await axios.get(`https://www.linkedin.com/oauth/v2/accessToken?grant_type=authorization_code&code=${code}&client_id=${process.env.CLIENTID}&client_secret=${process.env.CLIENT_SECRET}&redirect_uri=${process.env.REDIRECT_URI}`)
        
        // This request gets user info from access_token (given in the headers of the request)
        let userInfoResponse = await axios.get('https://api.linkedin.com/v2/me', {
            headers: {
                'Authorization': `Bearer ${accessTokenResponse.data.access_token}`
            }
        })

        return res.status(200).json(userInfoResponse.data)
    
    } catch (err) {
        console.log(err)
        return res.status(400).json({ message: 'Error authenticating' })
        
    }
})

app.listen(3001, () => console.log('Server started'))
```
Endpoint all setup! lets add the block to make a GET request to this endpoint in the `useEffect` of our frontend.
```javascript
// App.js
import axios from 'axios'
import { useEffect } from 'react'

export default function App() {

  const linkedinRedirectUrl = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${process.env.REACT_APP_CLIENTID}&redirect_uri=${process.env.REACT_APP_REDIRECT_URI}&scope=r_liteprofile,r_emailaddress`

  useEffect(() => {
    let windowUrl = window.location.href
    if (windowUrl.includes('code=')) {
      let codeMatch = windowUrl.match(/code=([a-zA-Z0-9_\-]+)/)
      
      axios.get('http://localhost:3001/user', {
        headers: {
          auth_code: codeMatch[1]
        }
      })
      .then(res => {
        console.log(res.data)
        
      })
      .catch(console.log)
      
    }

  }, [])

  const handleLinkedinLogin = () => {
    window.location.href = linkedinRedirectUrl
  }

  return (
    <div className="App">
      <button onClick={handleLinkedinLogin}>
        Login with LinkedIn
      </button>
    </div>
  );
}
```
*Tip: Try printing the `codeMatch` variable to get idea on whats going on in the `.match()` method

Now, lets once more try clicking the **"Login with LinkedIn"** button...
![badabing badaboom linked auth success](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x3aziu36f4o5wbhmhuq5.png)

![success](https://media.giphy.com/media/26xBE8LbggMqwH1PW/giphy.gif)