LinkedIn Reconcile Lambda

On this page

When a LinkedIn identity provider is used to complete a federated login request, FusionAuth will use the configured linking strategy to reconcile the user. FusionAuth will attempt to match the user information returned from the LinkedIn identity provider to an existing user account or create a new one.

You may optionally utilize a lambda to customize the user and user registration during the authentication event.

When you create a new lambda using the FusionAuth Admin UI, you will be provided an empty function to implement.

Starting in version 1.65.0, you can use attribute mappings to map IdP claims directly to FusionAuth user and registration fields without writing a lambda. Reconcile lambdas and attribute mappings are mutually exclusive. Attempting to configure both on a given IdP results in a validation error.

Lambda Structure#

When you create the lambda with the API, give the function the following signature:

function reconcile(user, registration, linkedInUser, context) {
  // Lambda code goes here
}

This lambda must contain a function named reconcile that accepts the following parameters:

  • user: the FusionAuth User object. You can modify this object. However, you can't change the email or username attributes after FusionAuth links the user.
  • registration: the FusionAuth UserRegistration object. You can modify this object.
  • linkedInUser: the JSON payload returned by the LinkedIn UserInfo endpoint. When the identity provider requests the legacy r_liteprofile, r_basicprofile, or r_emailaddress scope, FusionAuth calls the LinkedIn Profile API instead, and adds the email address as emailAddress. This object is read-only. For a full list of fields, see the LinkedIn UserInfo reference or the LinkedIn Profile API reference.
  • context: Available since 1.64.0 an object containing the context of the request, including access to secrets. This object is read-only.

The User API and Registration API documentation describe the user and registration objects. The fields in linkedInUser depend on the user's LinkedIn settings and the scopes in the identity provider configuration.

Assigning The Lambda#

After you create the lambda, assign the lambda to the LinkedIn identity provider.

Navigate to Settings -> Identity Providers . Select your existing LinkedIn configuration. If you haven't configured LinkedIn yet, click Add provider and select LinkedIn.

Default Lambda#

FusionAuth includes a default LinkedIn reconcile lambda. You can use the default lambda as is or change the lambda:

The default lambda handles two response formats. The format depends on the type of LinkedIn integration your application uses. See Set Up Your LinkedIn App Client Credentials for more information.

To see which format LinkedIn returns, print the linkedInUser object to the Event Log with console.log.

// This is the default LinkedIn reconcile, modify this to your liking.
function reconcile(user, registration, linkedInUser) {
  // Un-comment this line to see the linkedInUser object printed to the event log
  // console.info(JSON.stringify(linkedInUser, null, ' '));

  // Depending on how and when you have set up your LinkedIn application you may get a different response back in the linkedInUser.
  //
  // The first checks apply if you are using the "Sign In with LinkedIn using OpenID Connect" with the "openid", "email", and "profile" scopes.
  // If so FusionAuth will call the LinkedIn UserInfo API.
  // See https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2#api-request-to-retreive-member-details
  //
  // The second checks apply if you are using the legacy program and Profile API with the "r_liteprofile" or "r_basicprofile" scopes.
  // See https://learn.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api

  if (linkedInUser.given_name) {
    user.firstName = linkedInUser.given_name;
  } else if (linkedInUser.localizedFirstName) {
    user.firstName = linkedInUser.localizedFirstName;
  }

  if (linkedInUser.family_name) {
    user.lastName = linkedInUser.family_name;
  } else if (linkedInUser.localizedLastName) {
    user.lastName = linkedInUser.localizedLastName;
  }

  if (linkedInUser.picture) {
    // UserInfo will only supply one image size
    user.imageUrl = linkedInUser.picture;
  } else if (linkedInUser.profilePicture){
    // LinkedIn may return several images sizes.
    // See https://docs.microsoft.com/en-us/linkedin/shared/references/v2/profile/profile-picture
    // We'll sort the array by descending size and then grab the largest one.
    var images = linkedInUser.profilePicture['displayImage~'].elements || [];
    images.sort(function(a, b) {
      return b.data["com.linkedin.digitalmedia.mediaartifact.StillImage"].displaySize.width - a.data["com.linkedin.digitalmedia.mediaartifact.StillImage"].displaySize.width;
    });
    if (images.length > 0) {
      user.imageUrl = images[0].identifiers[0].identifier;
    }
  }
}

Example Lambda#

The following lambda reads the name from either LinkedIn response format, and keeps the saved name when LinkedIn leaves the name out. The lambda also sets the user's preferred language from the locale field. LinkedIn sends locale as a string such as en-US or as an object such as { "country": "US", "language": "en" }, and the lambda handles both. The lambda changes preferredLanguages only when the user has no preferred language yet.

A lambda that saves the LinkedIn profile and preferred language.
// fills in the profile from either LinkedIn response format and sets the preferred language
function reconcile(user, registration, linkedInUser, context) {
  // openid connect apps send given_name, legacy profile api apps send localizedFirstName
  user.firstName = linkedInUser.given_name || linkedInUser.localizedFirstName || user.firstName;
  user.lastName = linkedInUser.family_name || linkedInUser.localizedLastName || user.lastName;
  user.imageUrl = linkedInUser.picture || user.imageUrl;

  // locale is a string such as en-US, or an object such as { language: 'en', country: 'US' }
  var locale = linkedInUser.locale;
  var language = typeof locale === 'string' ? locale.split(/[-_]/)[0] : locale && locale.language;

  // keep a language the user already chose
  if (language && (!user.preferredLanguages || user.preferredLanguages.length === 0)) {
    user.preferredLanguages = [language];
  }
}