LinkedIn OAuth 2.0 Authorization Code Flow with PKCE: Secure Mobile Authentication Made Simple
Implement LinkedIn’s OAuth 2.0 Authorization Code Flow with PKCE to secure mobile authentication without a client secret. Learn the step‑by‑step flow, a Node.js example, token rotation, rate limits, and actionable next steps.
08 Aug 2025, 01:31 UTC

Why LinkedIn Needs PKCE for Mobile Apps
When a mobile or single‑page application wants to call LinkedIn’s APIs, it must prove it’s an authorized user. The classic OAuth 2.0 Authorization Code Flow requires a client secret – a value that can’t live safely inside a mobile binary. LinkedIn solves this by recommending the Authorization Code Flow with Proof Key for Code Exchange (PKCE). PKCE removes the need for a client secret and protects against authorization code interception attacks.
Key Concepts & Flow Overview
- Client ID – the public identifier of your LinkedIn app.
- Redirect URI – a registered endpoint that receives the authorization code.
- PKCE Code Challenge – a SHA‑256 hash of a random string (the code verifier).
- Authorization Code – a short‑lived token exchanged for an access token.
- Access Token – bearer token used for API requests, valid 60 days.
- Refresh Token – short‑lived, used to obtain new access tokens; must be rotated.
The flow proceeds as follows:
- Generate a random
code_verifierand compute its SHA‑256code_challenge. - Open the LinkedIn authorization URL with
client_id,redirect_uri,scope,code_challenge, andcode_challenge_method=S256. - After user consent, LinkedIn redirects to
redirect_uriwith anauthorization_code. - Exchange the code for an
access_tokenandrefresh_tokenat the token endpoint, sending the originalcode_verifier. - Use the
access_tokenfor API calls. When it expires, use therefresh_tokento obtain a new pair.
Concrete Example – Node.js Implementation
Below is a minimal, platform‑agnostic example using node-fetch. Replace placeholders with your real values.
// 1. Generate PKCE pair
const crypto = require('crypto');
const codeVerifier = crypto.randomBytes(32).toString('hex');
const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
// 2. Build the authorization URL
const clientId = 'YOUR_CLIENT_ID';
const redirectUri = 'https://yourapp.com/auth/linkedin/callback';
const scopes = ['r_liteprofile', 'r_emailaddress'];
const authUrl = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent(scopes.join(' '))}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
// 3. After user completes consent, LinkedIn redirects to redirectUri with ?code=...
// 4. Exchange code for tokens
async function exchangeCode(code) {
const tokenUrl = 'https://www.linkedin.com/oauth/v2/accessToken';
const body = new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: redirectUri,
client_id: clientId,
code_verifier: codeVerifier
});
const res = await fetch(tokenUrl, { method: 'POST', body });
const data = await res.json();
if (!res.ok) throw new Error(`Token request failed: ${data.error_description}`);
return data; // contains access_token, expires_in, refresh_token
}
Run exchangeCode('AUTHORIZATION_CODE_FROM_QUERY') after capturing the code from the redirect. Store the refresh_token securely; it must be rotated after each use.
Token Rotation & Revocation
LinkedIn issues a new refresh_token every time you exchange an authorization code or a refresh token. The old token becomes invalid. Store the latest token and discard the previous one. If you need to force a logout, call the revocation endpoint:
async function revokeToken(token) {
const revokeUrl = 'https://www.linkedin.com/oauth/v2/revoke';
const body = new URLSearchParams({
token,
client_id: clientId
});
const res = await fetch(revokeUrl, { method: 'POST', body });
if (!res.ok) throw new Error('Revocation failed');
}
After revocation, any API call with that token will return 401 Unauthorized. Verify by attempting a simple profile request.
Rate Limits & Monitoring
LinkedIn allows roughly 10,000 token requests per hour per app. Exceeding this quota triggers HTTP 429 (Too Many Requests) and a temporary suspension. The response includes X-RateLimit-Remaining and Retry-After headers. Log these headers and back‑off when needed.
Use the LinkedIn Dashboard to monitor API usage and identify spikes. If you notice sudden increases, check whether your app inadvertently re‑requests tokens on every app launch.
Trade‑offs & Limitations
- No Client Secret – PKCE removes the secret, but it also means you can’t use the client‑credentials flow for server‑to‑server calls. For those, you must host a secure backend that stores the secret.
- Short‑Lived Refresh Tokens – The refresh token expires quickly (often 24‑48 hours). Your app must handle rotation gracefully, otherwise users will be prompted to re‑authenticate.
- Scope Changes – LinkedIn occasionally deprecates scopes. Test your app after each LinkedIn SDK or policy update.
Actionable Next Steps
- Register your app on LinkedIn Developers and enable OAuth 2.0 with PKCE.
- Implement the PKCE flow in your mobile or SPA, following the example above.
- Store the
refresh_tokensecurely and rotate it on each exchange. - Set up logging for
X-RateLimit-RemainingandRetry-Afterheaders. - Use the revocation endpoint when a user logs out or you need to invalidate tokens.
- Periodically review the LinkedIn API usage dashboard and update scopes as needed.
By following this pattern, you keep the authentication flow secure, avoid exposing a client secret, and stay compliant with LinkedIn’s rate‑limit and token‑management policies.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.