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 needing a linkedin app which would represent our web app for authentication to the user (create the linkedin app from here). We also need a page for our app... (I know, this sh*t takes ages 😑)
After creating a page with minimal details and pasting the page url in the input box, we are ALMOST done creating the app
Now if we see our app on the url: https://www.linkedin.com/developers/apps, it should contain a section named "Auth"
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.
*Note: Redirect uri is the absolute url path of the part of your web app on which you want linkedin to redirect to after authentication.
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.
I suggest to store the redirect uri in an environment variable in your app as we will be requiring it quite often and also env variables are suggested way to organise fundamental constant variables which allow us to use them in different environments with different values (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.
To give permissions to our app, we need to go in Products tab > Sign In with LinkedIn and click on Select > Add Product. LinkedIn will review our app and after a few moments, the product should be added to it (this sh*t takes ages 😑). Once complete, it will be reflected in the "Auth" tab
Phew! that was a lot of setup! lets move on to the fun part ;)
Authentication Flow
LinkedIn auth has a 3-step authentication process:
- Get the authorization
code
(done on frontend) - Exchange the code to get an
access_token
(requires backend) - Exchange the
access_token
to get user's details (email, name etc.) (requires backend)
What I mean by requiring a backend?
Responses from the requests to be made in the step 2 & 3 don't have an Access-Control-Allow-Origin
header. 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.
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:
(the state
parameter is not required and neither we will be using it)
Your final url to redirect user on, should look like this:
`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.
// 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>
);
}
IT WORKS!
Clicking on Allow will redirect to home page of our app, but in the address bar there's something different!
Thats THE CODE!
Step 2 (Retrieving the authorization_code
)
Now, in this step we must retrieve the code after the redirection 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.
// 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, it just means to pickup a group of characters which are lowercase alphabets (a-z), uppercase alphabets (A-Z), underscores & hyphens. 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 backend code for the endpoint which will fetch the access_token
& user info.
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.
// 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...
Top comments (1)
Finally a great tutorial! Thank you for the effort!!
By the way - it is important to note that not using "state" parameter exposes you to csrf attack.