Retrieve vanity url and headline from LinkedIn with NextAuth (using r_basicprofile)
Tags: linkedin, nextjs, nextauth
Published: 2023-10-23 07:48:11

Hi all

For [Headlinker](https://headlinker.com/fr) the community for indie recruiters i'm building right now, I set up a LinkedIn login capability, as all of my users do have LinkedIn.

It's very easy to setup to get the **email**, **name** and **profile image** but not quite easy to get the **vanity url** and **headline**.

So here is how to do it!

## Prerequisites

You have [NextAuth](https://next-auth.js.org/providers/linkedin) already setup in your app with basic [LinkedIn](https://next-auth.js.org/providers/linkedin).

## Permissions

By default, when asking for login permissions on LinkedIn, you get `r_liteprofile` and `r_emailaddress` which gives you basically first name, last name, image and email.

We need to get access to `r_basicprofile` to get back the **vanity URL** and the **headline**.

For this 
- Go to your [LinkedIn Developer apps page](https://www.linkedin.com/developers/apps)
- Select you app
- Go to `Products`
- Ask for permissions on the `Advertising API` (Yes it's weird but it's in there)

![Advertising API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bfcc6jqf9iw17hehury4.png)

- Answer the questions and be as specific as possible on why you need access to these informations
- Wait

It can take several hours to get access or you can even get refused.
If you manage though, you can go to next step.

## Configure Next-Auth

No that you have access to those new fields, you need to update next-auth to actually retrieve those fields and save them in your database.

For this, you will need to replace
```js
LinkedInProvider({
  clientId: process.env.AUTH_LINKEDIN_CLIENT_ID as string,
  clientSecret: process.env.AUTH_LINKEDIN_CLIENT_SECRET as string,
}),
```


by

```js
{
  ...LinkedInProvider({
    clientId: process.env.AUTH_LINKEDIN_CLIENT_ID as string,
    clientSecret: process.env.AUTH_LINKEDIN_CLIENT_SECRET as string,
  }),
  authorization: {
    url: 'https://www.linkedin.com/oauth/v2/authorization',
    params: { scope: 'r_liteprofile r_basicprofile r_emailaddress' },
  },
  userinfo: {
    url: 'https://api.linkedin.com/v2/me',
    params: {
      projection: `(id,localizedFirstName,localizedLastName,vanityName,localizedHeadline,profilePicture(displayImage~digitalmediaAsset:playableStreams))`,
    },
  },
  async profile(profile, tokens) {
    const emailResponse = await fetch('https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))', {
      headers: { Authorization: `Bearer ${tokens.access_token}` },
    });
    const emailData = await emailResponse.json();
    return {
      id: profile.id,
      url: `https://www.linkedin.com/in/${profile.vanityName}`,
      name: `${profile.localizedFirstName} ${profile.localizedLastName}`,
      email: emailData?.elements?.[0]?.['handle~']?.emailAddress,
      title: profile.localizedHeadline,
      image: profile.profilePicture?.['displayImage~']?.elements?.[0]?.identifiers?.[0]?.identifier,
    };
  },
  allowDangerousEmailAccountLinking: true,
```

Let's break this down
- First, change the scope and add `r_basicprofile`, this will give access to the corresponding fields
- Then, change the projection in **userinfo** so that it includes the **vanityName** (martinratinaud) which we will use to build the [vanity URL](https://www.linkedin.com/in/martinratinaud) and the **localizedHeadline**
- Finally, map the profile to where you want to store it in your database -> for me, **url** and **title**
- Optionally, set `allowDangerousEmailAccountLinking` if you already have LinkedIn account in the database. If it's the case and you don't do it, users will get an error as they can't signin with different permissions. See [https://next-auth.js.org/configuration/providers/oauth#allowdangerousemailaccountlinking-option](official doc) regarding this matter.


## Bonus
In order to find all the fields available you just need to empty  `userinfo.params.projection` and _console.log_ in `async profile` function.