DEV Community

Martin Ratinaud
Martin Ratinaud

Posted on

Retrieve vanity url and headline from LinkedIn with NextAuth (using r_basicprofile)

Hi all

For Headlinker 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 already setup in your app with basic 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
  • Select you app
  • Go to Products
  • Ask for permissions on the Advertising API (Yes it's weird but it's in there)

Advertising API

  • 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

LinkedInProvider({
  clientId: process.env.AUTH_LINKEDIN_CLIENT_ID as string,
  clientSecret: process.env.AUTH_LINKEDIN_CLIENT_SECRET as string,
}),
Enter fullscreen mode Exit fullscreen mode

by

{
  ...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,
Enter fullscreen mode Exit fullscreen mode

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 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 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.

Top comments (0)