Implementing LinkedIn OAuth 2.0 with PKCE in a Native App
Learn how to secure LinkedIn authentication for a native app using PKCE, handle token storage, respect rate limits, and decide when to refresh or re-authenticate.
06 Dec 2025, 16:05 UTC

Why LinkedIn PKCE Matters for Native Apps
LinkedIn’s OAuth 2.0 endpoint follows RFC 6749 and RFC 7636, meaning a native or mobile client can safely obtain an access token without embedding a client secret. The Proof Key for Code Exchange (PKCE) extension mitigates interception of the authorization code by requiring the client to prove it generated the code. This is essential because native apps cannot keep a secret on the device; any secret embedded in the binary can be extracted via reverse engineering.
The Integration Challenge
When building a native desktop or mobile application that calls LinkedIn’s REST APIs, you need a short-lived bearer token that authorizes the user. The challenge is to obtain that token securely, store it safely, refresh it when it expires, and avoid hitting LinkedIn’s rate limits. The solution is to implement the full PKCE-enabled OAuth 2.0 flow and follow LinkedIn’s token-handling recommendations.
Step 1 – Register Your Application
- Log into the LinkedIn Developer Portal and create a new application.
- Under Auth, set the OAuth 2.0 Redirect URLs to a custom scheme you control, e.g.,
myapp://authfor iOS orcom.mycompany.myapp://oauthfor Android. - Enable the scopes your app needs. For profile read, you typically need
r_liteprofile; for email,r_emailaddress; for posting,w_member_social. - Copy the Client ID; do not embed a client secret into the native app.
Step 2 – Build the Authorization URL
The user must be redirected to LinkedIn’s authorization endpoint. The URL contains the client ID, redirect URI, requested scopes, response type, and the PKCE code challenge.
https://www.linkedin.com/oauth/v2/authorization?
response_type=code
&client_id=<CLIENT_ID>
&redirect_uri=<REDIRECT_URI>
&scope=r_liteprofile%20r_emailaddress
&state=<RANDOM_STATE>
&code_challenge=<CODE_CHALLENGE>
&code_challenge_method=S256
Generate the code_verifier (a high-entropy random string) and its SHA-256 hash (the code_challenge) before opening the URL. The state parameter protects against Cross-Site Request Forgery (CSRF); store it locally and verify it matches the response.
Python Example – Generating PKCE Values
import os, hashlib, base64
def generate_pkce():
# Create a high-entropy random string
verifier = base64.urlsafe_b64encode(os.urandom(40)).rstrip(b"=")
# Hash the verifier using SHA-256
challenge = base64.urlsafe_b64encode(
hashlib.sha256(verifier).digest()
).rstrip(b"=")
return verifier.decode(), challenge.decode()
code_verifier, code_challenge = generate_pkce()
Step 3 – Receive the Authorization Code
Your native app must listen for the redirect URI. On iOS, use a custom URL scheme; on Android, register an intent-filter. Extract the code and state from the query string. Verify the state matches what you stored earlier. Any mismatch should abort the flow to prevent session hijacking.
Step 4 – Exchange the Code for Tokens
Send a POST request to LinkedIn’s token endpoint. The request must include the original code_verifier and the code you just received.
POST https://www.linkedin.com/oauth/v2/accessToken
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
code=<AUTHORIZATION_CODE>
redirect_uri=<REDIRECT_URI>
client_id=<CLIENT_ID>
code_verifier=<CODE_VERIFIER>
LinkedIn responds with a JSON payload containing the access_token and refresh_token. Store these in encrypted secure storage (Keychain on iOS, Keystore on Android, or OS-level credential stores on desktop).
cURL Example – Token Exchange
curl -X POST \
https://www.linkedin.com/oauth/v2/accessToken \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=RECEIVED_CODE" \
-d "redirect_uri=myapp://auth" \
-d "client_id=YOUR_CLIENT_ID" \
-d "code_verifier=YOUR_CODE_VERIFIER"
Step 5 – Use and Refresh the Access Token
Include the token as a bearer token in the Authorization header of all API calls. LinkedIn returns rate-limit headers (e.g., X-RateLimit-Remaining). Implement logic to back off when this value is low.
When the access token expires, exchange the refresh_token for a new pair:
POST https://www.linkedin.com/oauth/v2/accessToken
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
refresh_token=<REFRESH_TOKEN>
client_id=<CLIENT_ID>
Trade-Offs and Limitations
- Security vs. Convenience: PKCE removes the need for a client secret, but you must still protect the
code_verifierin memory until the exchange is complete. - Refresh Token Expiry: Refresh tokens can expire after a period of inactivity. Implement a graceful fallback that prompts the user to re-authenticate rather than simply crashing or returning a generic error.
- Rate Limits: Exceeding per-client limits can temporarily block your app. Always monitor
X-RateLimit-Remainingand implement exponential back-off.
Actionable Checklist
- Register the app and enable OAuth 2.0 with a custom redirect URI.
- Implement PKCE to generate a secure
code_verifierandcode_challenge. - Handle the redirect to capture the authorization code and verify the
state. - Exchange the code for tokens and store them in encrypted device storage.
- Wrap API calls with the bearer token and monitor rate-limit headers.
- Implement a refresh logic and a fallback for expired refresh tokens.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.