LinkedIn authentication strategy for Passport using OAuth 1.0a
npx @tessl/cli install tessl/npm-passport-linkedin@1.0.0passport-linkedin is a Passport authentication strategy for integrating LinkedIn OAuth 1.0a authentication into Node.js applications. It allows applications to authenticate users through their LinkedIn accounts with configurable permission scopes and profile field selection.
npm install passport-linkedin// Default import (recommended)
const LinkedInStrategy = require('passport-linkedin');Alternative imports:
// Named import
const { Strategy } = require('passport-linkedin');
// Explicit Strategy property access
const LinkedInStrategy = require('passport-linkedin').Strategy;const passport = require('passport');
const LinkedInStrategy = require('passport-linkedin');
passport.use(new LinkedInStrategy({
consumerKey: 'YOUR_LINKEDIN_API_KEY',
consumerSecret: 'YOUR_LINKEDIN_SECRET_KEY',
callbackURL: 'http://localhost:3000/auth/linkedin/callback'
},
function(token, tokenSecret, profile, done) {
// User authentication logic
User.findOrCreate({ linkedinId: profile.id }, function (err, user) {
return done(err, user);
});
}
));
// Routes
app.get('/auth/linkedin', passport.authenticate('linkedin'));
app.get('/auth/linkedin/callback',
passport.authenticate('linkedin', { failureRedirect: '/login' }),
function(req, res) {
// Successful authentication
res.redirect('/');
});passport-linkedin extends Passport's OAuth 1.0a strategy with LinkedIn-specific customizations:
passport-oauth1 for standard OAuth 1.0a protocol handlingCreates a new LinkedIn authentication strategy for Passport.
/**
* LinkedIn authentication strategy constructor
* @param {Object} options - Configuration options
* @param {Function} verify - Verification callback function
*/
function Strategy(options, verify);Options:
interface StrategyOptions {
/** LinkedIn API consumer key (required) */
consumerKey: string;
/** LinkedIn API consumer secret (required) */
consumerSecret: string;
/** OAuth callback URL (required) */
callbackURL: string;
/** Specific profile fields to request (optional) */
profileFields?: string[];
/** Request token URL (optional) */
requestTokenURL?: string;
/** Access token URL (optional) */
accessTokenURL?: string;
/** User authorization URL (optional) */
userAuthorizationURL?: string;
/** Session key for OAuth tokens (optional) */
sessionKey?: string;
}Verify Callback:
/**
* Verification callback function
* @param {string} token - OAuth access token
* @param {string} tokenSecret - OAuth token secret
* @param {Object} profile - User profile object
* @param {Function} done - Completion callback
*/
function verify(token, tokenSecret, profile, done);Request extended permissions using the scope parameter during authentication.
// Single scope
app.get('/auth/linkedin',
passport.authenticate('linkedin', { scope: 'r_fullprofile' }));
// Multiple scopes
app.get('/auth/linkedin',
passport.authenticate('linkedin', {
scope: ['r_basicprofile', 'r_emailaddress']
}));Configure specific profile fields to request from LinkedIn API.
passport.use(new LinkedInStrategy({
consumerKey: 'YOUR_API_KEY',
consumerSecret: 'YOUR_SECRET',
callbackURL: 'http://localhost:3000/auth/linkedin/callback',
profileFields: ['id', 'first-name', 'last-name', 'email-address', 'headline']
},
function(token, tokenSecret, profile, done) {
// Handle authentication
return done(null, profile);
}
));Handles the OAuth authentication flow and processes LinkedIn responses. Extends the base OAuth1 strategy authentication with LinkedIn-specific error handling.
/**
* Authenticate request using LinkedIn OAuth
* @param {Object} req - HTTP request object
* @param {Object} options - Authentication options (scope, etc.)
* @returns {void} - Calls success(), fail(), or error() on completion
*/
Strategy.prototype.authenticate(req, options);Special behavior:
oauth_problem=user_refused query parameter and calls fail() for user denialRetrieves user profile information from LinkedIn API.
/**
* Retrieve user profile from LinkedIn
* @param {string} token - OAuth access token
* @param {string} tokenSecret - OAuth token secret
* @param {Object} params - Additional parameters
* @param {Function} done - Completion callback with (err, profile)
*/
Strategy.prototype.userProfile(token, tokenSecret, params, done);Returns LinkedIn-specific parameters for request token requests, with special handling for scope parameters.
/**
* Return extra LinkedIn-specific parameters for request token request
* @param {Object} options - Options containing scope information
* @param {string|string[]} options.scope - LinkedIn permission scope(s)
* @returns {Object} Parameters object with scope handling
*/
Strategy.prototype.requestTokenParams(options);Scope handling:
'r_basicprofile'['r_basicprofile', 'r_emailaddress'] → 'r_basicprofile+r_emailaddress'The profile object returned by LinkedIn contains normalized user information:
interface LinkedInProfile {
/** Always 'linkedin' */
provider: string;
/** LinkedIn user ID */
id: string;
/** Full display name (firstName + lastName) */
displayName: string;
/** Name breakdown */
name: {
/** Last name */
familyName: string;
/** First name */
givenName: string;
};
/** Email addresses (if requested via profileFields) */
emails?: Array<{
/** Email address value */
value: string;
}>;
/** Raw API response body */
_raw: string;
/** Parsed JSON response */
_json: {
/** LinkedIn user ID */
id: string;
/** First name from LinkedIn */
firstName: string;
/** Last name from LinkedIn */
lastName: string;
/** Email address (if requested) */
emailAddress?: string;
/** Additional fields based on profileFields configuration */
[key: string]: any;
};
}interface StrategyInstance {
/** Strategy name, always 'linkedin' */
name: string;
}Standard profile field names are mapped to LinkedIn API fields:
'id' → 'id''name' → ['first-name', 'last-name']'emails' → 'email-address'Unmapped field names are passed directly to the LinkedIn API, allowing access to additional LinkedIn profile data.
The strategy internally converts profile field names using a mapping system:
/**
* Internal method to convert profile field names to LinkedIn API format
* @param {string[]} profileFields - Array of profile field names
* @returns {string} Comma-separated LinkedIn API field names
* @private
*/
Strategy.prototype._convertProfileFields(profileFields);Example conversions:
['id', 'name', 'emails'] → 'id,first-name,last-name,email-address'['id', 'headline', 'industry'] → 'id,headline,industry' (unmapped fields passed through)This enables flexible profile data collection while maintaining compatibility with standard field names.
The strategy handles common error scenarios:
oauth_problem=user_refused parameter and calls fail()InternalOAuthError from passport-oauth1/**
* OAuth-specific error wrapper from passport-oauth1
* @param {string} message - Error description
* @param {Error} err - Original error object
*/
class InternalOAuthError extends Error {
constructor(message, err);
/** OAuth error details */
oauthError: Error;
}Common LinkedIn permission scopes:
r_basicprofile - Basic profile informationr_emailaddress - Email addressr_fullprofile - Full profile accessw_messages - Send messagesrw_company_admin - Company administrationMultiple scopes are joined with '+' when sent to LinkedIn API.