Adding Sign in with LinkedIn via OpenID Connect: What You Actually Get and What to Watch For
Learn how LinkedIn's OpenID Connect login works, what user data you actually receive, and the engineering trade-offs to plan for.
25 May 2026, 21:38 UTC

The problem: professional login without the professional data
You need a sign-in option that feels natural for a B2B audience. LinkedIn's "Sign in with LinkedIn using OpenID Connect" looks like the obvious choice, but the OIDC layer only returns a handful of claims — name, picture, email, and a stable subject identifier. Richer profile fields (positions, skills, organization membership) live behind separate LinkedIn API products that require their own review process. Treating login and data access as a single workstream leads to scope creep and surprise rate-limit errors.
How the OIDC authorization-code flow works with LinkedIn
- Redirect the user to LinkedIn's authorization endpoint with
response_type=code,scope=openid profile email, a randomstate, and anonce. - Receive an authorization code at your redirect URI.
- Exchange the code server-side at the token endpoint using your client secret. The response contains an
access_token, anid_token(JWT), and optionally arefresh_token. - Validate the ID token (signature, issuer, audience, expiry, nonce) and optionally call the
userinfoendpoint with the access token for the same claim set.
Scopes and claims you actually receive
| Scope | Claims returned (ID token / userinfo) |
|---|---|
openid | sub (stable user ID), iss, aud, exp, iat, nonce |
profile | name, given_name, family_name, picture |
email | email, email_verified (boolean) |
No claim for headline, current company, or connections. If your onboarding needs those, you must apply for separate LinkedIn API products such as profile or marketing data access.
Practical engineering decisions
Account linking: key on sub, not email
Emails can change or be missing (unverified). Using sub as the primary key prevents duplicate accounts and hijacking when a user updates their primary email on LinkedIn.
Token handling
- Store only the short-lived
access_tokenin memory or a secure session store; never expose it to the browser. - Keep the
client_secretstrictly server-side. The authorization code flow is designed for confidential clients. - Validate the ID token against LinkedIn's JWKS on every login.
Standard OIDC hardening
- Generate a cryptographically random
stateper request and verify it on callback. - Send a
nonceand verify it appears unchanged in the ID token. - Check
issequalshttps://www.linkedin.comandaudmatches your client ID.
Worked example: token exchange and ID-token validation (Node.js)
// 1. Authorization redirect (run in browser)
const authUrl = 'https://www.linkedin.com/oauth/v2/authorization?' +
new URLSearchParams({
response_type: 'code',
client_id: 'YOUR_CLIENT_ID',
redirect_uri: 'https://app.example.com/callback',
scope: 'openid profile email',
state: 'RANDOM_STATE_STRING',
nonce: 'RANDOM_NONCE_STRING'
}).toString();
// → redirect user to authUrl
// 2. Callback handler (server-side)
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
if (state !== req.session.oidcState) return res.status(400).send('Invalid state');
// Exchange code for tokens
const tokenResp = await fetch('https://www.linkedin.com/oauth/v2/accessToken', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: 'https://app.example.com/callback',
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET'
})
});
const { access_token, id_token } = await tokenResp.json();
// 3. Validate ID token (using jose library)
const { createRemoteJWKSet, jwtVerify } = await import('jose');
const JWKS = createRemoteJWKSet(new URL('https://www.linkedin.com/oauth/openid/jwks'));
const { payload } = await jwtVerify(id_token, JWKS, {
issuer: 'https://www.linkedin.com',
audience: 'YOUR_CLIENT_ID',
nonce: req.session.oidcNonce
});
// payload now contains sub, name, email, email_verified, …
req.session.user = { sub: payload.sub, email: payload.email, name: payload.name };
res.redirect('/dashboard');
});
Run the redirect in a browser; the callback runs on your server with the client_secret kept out of the frontend. Replace placeholders with your actual LinkedIn app credentials. This example was not executed against the live API; verify endpoint URLs and response fields against LinkedIn's current OIDC discovery document before shipping.
Trade-offs and limitations
- Limited claim set — you cannot build a full professional profile from login alone.
- Separate product approvals — richer data needs distinct LinkedIn API products, each with its own review timeline.
- Dependency risk — LinkedIn has deprecated older sign-in endpoints (e.g., the pre-OIDC
r_liteprofileflow). Pin your integration to the current OIDC discovery document and monitor the developer changelog. - Rate limits and availability — token endpoint and userinfo calls are subject to LinkedIn's platform limits; implement exponential back-off and a fallback sign-in method (email/password or another IdP).
Actionable next steps
- Create a LinkedIn developer app, enable "Sign in with LinkedIn using OpenID Connect", and note the client ID and secret.
- Fetch the OIDC discovery document and JWKS; confirm your validation library accepts the issuer
https://www.linkedin.comand your client ID as audience. - Run the full authorization-code flow in a staging environment; log the raw ID token and userinfo response to verify the exact claims returned for a test account.
- Implement account linking on
suband add an alternative sign-in method for resilience.
By treating LinkedIn OIDC as an authentication primitive — not a data source — you keep the integration simple, auditable, and ready for the inevitable product changes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.