LinkedIn Login
LinkedIn Login lets a domain's users sign in to the Hub with their LinkedIn account instead of (or alongside) a Hub password. It is one branch of a shared federation framework that also drives SAML SSO — there is no LinkedIn-specific route or Passport strategy registered up front; the correct provider is resolved dynamically, per request, from the domain's configuration.
Supported Features
- OAuth2 / OpenID Connect sign-in: uses LinkedIn's
openid profile emailscopes viapassport-linkedin-oauth2. - Per-domain configuration: each domain can have its own LinkedIn app credentials (
clientID/clientSecret), configured by an admin in Admin → Settings. - Find-or-create user matching: on first login, a user is created (or matched to an existing account) using the LinkedIn-provided email address; existing users are updated and reactivated if previously soft-deleted.
- State passthrough: an incoming
RelayStatequery parameter (the same mechanism used for SAML) is forwarded as the OAuth2state, so post-login redirects work the same way for both providers.
Implementation Details
Repo: 5app/hub
Configuration
Federation settings live in the domainFederations table (api/db/models/domainFederations.js). The relevant column is type, which is either 'saml' or 'linkedin'. For LinkedIn, clientID and clientSecret hold the LinkedIn app credentials; the SAML-only columns (entryPoint, cert, etc.) are unused.
Admins configure this per domain in Admin → Settings (frontend/src/admin/pages/settings/index.js, settings_sso_* translation keys). The frontend login page (frontend/src/main/components/pages/auth/login.js) reads the resulting config.federatedLogins list and renders an SSO button per entry, linking to:
/auth/federation/{id}/login?RelayState=<base64-encoded state>
Routing (auth service)
auth/routes/index.js mounts auth/routes/federation.js at /auth/federation, wiring login, callback, logout, and metadata handlers (auth/routes/auth/federation/*). Each of these calls passport.authenticate('federation', ...).
Dynamic strategy resolution
'federation' is not a normal, statically-configured Passport strategy — it's a custom Strategy (auth/modules/federation/index.js) whose authenticate() method:
- Looks up the
domainFederationsrow for the current subdomain (and optional:idpath param). - Reads
typeand builds the corresponding sub-strategy on the fly:linkedin→auth/modules/federation/linkedin.jssaml(default) →auth/modules/federation/saml.js
- Delegates
authenticate()to that sub-strategy instance, wiringsuccess/fail/error/redirect/passthrough from the outer Passport context so the sub-strategy behaves as if it were registered directly.
LinkedIn strategy (auth/modules/federation/linkedin.js)
- Overrides
LinkedInStrategy.prototype.userProfileto call LinkedIn's OpenID ConnectGET /v2/userinfoendpoint directly (rather than the library's default profile endpoint), mappingsub/given_name/family_name/name/emailinto a Passport-shaped profile (provider: 'linkedin',id,name,displayName,emails). - Overrides
authenticateto readreq.query.RelayStateand forward it as the OAuth2stateparam, matching the SAML RelayState convention used elsewhere in the login flow. - Requests scopes
openid profile email. - On a successful callback, its verify function builds:
and hands this off to the shared verify function below. The{nameID: profile.id,first_name: profile.name.givenName,last_name: profile.name.familyName,email: profile.emails.pop().value,federationId,provider: 'linkedin',}
provider: 'linkedin'marker is what makes LinkedIn logins distinguishable from SAML ones once persisted (see LinkedIn login signal for Helix).
Shared verify function (auth/modules/auth.js)
Both SAML and LinkedIn logins funnel into the same auth.federation(req, meta, done) function:
- Resolves the
domainFederationsconfig for the subdomain (byfederationId, or the legacy defaultlegacy_callback_urlrow). - Derives the user's email from
meta.emailaddress/meta.email/ (optionally)meta.nameID. - Calls an internal
getOrSetUser(...)helper, which:- Finds an existing user by
name_id, thenemployeeid, thenemail— or creates a new one if none match (unlessdisableAutoCreateUseris set). - Sets
users.federated = trueand stores the full incomingmetapayload verbatim in theusers.metaJSON column — LinkedIn logins setprovider: 'linkedin', SAML logins setprovider: 'saml'. - Assigns any SSO-driven team memberships and reactivates a soft-deleted
userDomainsrecord if the user is rejoining.
- Finds an existing user by
- Loads the full "auth user" record and completes the Passport login via
done(null, authUser), logging aSUCCESSFUL_LOGINactivity event.
Session persistence (serializeUser / deserializeUser, same module) is identical to local username/password login — Redis-backed via the shared modules/ session middleware.
LinkedIn login signal for Helix
users.federated alone can't identify the provider — it's true for both SAML and LinkedIn logins. 5app/hub#17406 added a dedicated, reliable signal instead:
- Persisted marker:
users.meta.provider === 'linkedin', set on every LinkedIn login (see Shared verify function above). SAML logins setprovider: 'saml'instead, so the two are always distinguishable. - API:
GET /api/me/profile(api/routes/me/profile/get.js) readsusers.metaand returnsisLinkedinLogin: booleanin the response —trueonly whenmeta.provider === 'linkedin'. - Frontend:
useFetchUserProfile()exposesisLinkedinLoginon theUserProfiletype (frontend/src/common/types/users.ts).useMindsetLocationContext.tsincludes it (stringified, alongsidehasMeetingAnalysisand the other boolean signals) in the context object passed toagentEl.setSituationalAwareness(...), so the Helix/Mindset agent widget can see whether the current user is logged in via LinkedIn.
Note: this signal reflects the user's most recent federation login, not necessarily their current session — users.meta is overwritten (not merged) on every federation login, and isn't touched by local password logins, so a user who has ever logged in via LinkedIn keeps isLinkedinLogin: true until they log in via SAML or another federated method.
Activating for a hub
The 5app LinkedIn app for SSO is located at LinkedIn Developers app.
To activate LinkedIn login for a hub, add a new entry to the domainFederations DB table:
domain_id: {hub domain id}
identity: linkedin
type: linkedin
config: {"preferred": true, "buttonSubtitle": "LinkedIn", "domainAuthority": ""}
clientID: {LinkedIn app client id}
clientSecret: {LinkedIn app client secret}
signatureAlgorithm: sha256
is_deleted: 0
legacy_callback_url: 1
NB. Currently you must set legacy_callback_url to 0 for any other entries in domainFederations with the same domain_id.
Further Resources
- 5app/hub#17406 — added the
isLinkedinLoginsignal for Helix. - passport-linkedin-oauth2 — the underlying Passport strategy library.
- LinkedIn OpenID Connect docs —
/v2/userinfoand scope reference.