Sign In With LinkedIn Using OpenID Connect in Next.js 16
Sign In With LinkedIn Using OpenID Connect in Next.js 16
Current Situation Analysis
LinkedIn's migration from legacy OAuth v1/v2 to OpenID Connect (OIDC) has introduced significant friction for developers maintaining older authentication flows. The primary pain points stem from deprecated scopes (r_liteprofile, r_emailaddress) that silently break callback handlers after a grace period, making them exceptionally difficult to debug. Traditional tutorials still reference the /v2/me endpoint and manual JSON parsing, which are incompatible with modern OIDC implementations.
In Next.js 16 environments using NextAuth v5, the built-in LinkedIn provider defaults to these legacy configurations, forcing developers to manually override endpoints, issuers, and scope parameters. Additionally, LinkedIn's API ecosystem is highly fragmented: OIDC Sign-In only grants identity claims, while posting, feed reading, and community management require separate product approvals and distinct scope sets. Combined with aggressive consent caching and a 60-day token lifecycle without automatic refresh by default, the old OAuth dance creates a brittle authentication layer that fails unpredictably in production.
WOW Moment: Key Findings
The transition to OIDC standardizes identity verification but introduces distinct operational characteristics compared to legacy OAuth. Benchmarking the migration reveals clear trade-offs in setup complexity, token handling, and consent management.
| Approach | Scopes Required | User Info Endpoint | Identity Source | Token Lifespan | Setup Complexity | Consent Caching |
|---|---|---|---|---|---|---|
| Legacy OAuth v1/v2 | r_liteprofile r_emailaddress |
/v2/me |
Custom JSON payload | 60 days (no refresh) | High (manual field mapping) | Moderate |
| OIDC v2 (2026) | openid profile email |
/v2/userinfo |
Standard ID Token claims | 60 days (refresh via offline_access) |
Low (standardized claims) | Aggressive (requires prompt=consent) |
Key Findings:
- OIDC eliminates endpoint-specific calls for profile/email data by embedding claims directly in the ID token.
- The
/v2/userinfoendpoint strictly requires OIDC-compliant scopes; legacy scopes return403 Forbiddenor malformed payloads. - Consent caching behavior is significantly more aggressive in OIDC, necessitating explicit
prompt=consentduring development to validate scope changes.
Core Solution
The production-ready implementation requires aligning NextAuth v5 with LinkedIn's OIDC specification, configuring the correct callback route, and implementing a token persistence strategy that accounts for the 60-day expiry window.
1. OIDC Scopes & Token Architecture
LinkedIn's current Sign-In product requires exactly three lowercase, space-separated scopes:
openid profile email
This request returns a standard OIDC ID token (containing user identity claims) and a standard access token. All profile and email data are delivered via token claims, eliminating legacy profile-specific or email-specific API calls.
2. NextAuth v5 Provider Configuration
NextAuth v5 ships with a legacy-scoped LinkedIn provider. You must override the issuer, endpoints, and scope parameters to align with OIDC:
// src/lib/auth.ts
import LinkedIn from "next-auth/providers/linkedin";
export const { auth, handlers, signIn, signOut } = NextAuth({
providers: [
LinkedIn({
clientId: process.env.LINKEDIN_CLIENT_ID!,
clientSecret: process.env.LINKEDIN_CLIENT_SECRET!,
issuer: "https://www.linkedin.com",
authorization: {
params: { scope: "openid profile email" },
},
token: "https://www.linkedin.com/oauth/v2/accessToken",
userinfo: "https://api.linkedin.com/v2/userinfo",
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
};
},
}),
],
});
Critical Implementation Notes:
- The
userinfoendpoint must be/v2/userinfo, not the legacy/v2/me. - Use
profile.subas the stable LinkedIn user ID.profile.iddoes not exist on the OIDC endpoint and will resolve toundefined.
3. Callback URL Registration
In the LinkedIn Developer Portal, register the exact callback path:
https://your-domain.com/api/auth/callback/linkedin
Omitting the trailing path results in a generic "Bummer, something went wrong" error page with zero debug telemetry, often leading to false environment variable diagnostics.
4. Access Token Persistence & Lifecycle Management
The OIDC ID token handles authentication; the access token enables subsequent LinkedIn API calls (e.g., feed posting). NextAuth v5 exposes both in the jwt callback. Persist the access token and expiry timestamp to your user database row:
callbacks: {
async jwt({ token, account }) {
if (account?.provider === "linkedin") {
token.linkedinAccessToken = account.access_token;
token.linkedinExpiresAt = account.expires_at;
}
return token;
},
}
LinkedIn access tokens are valid for 60 days. While refresh tokens are supported, they require the offline_access scope, which is unnecessary for most applications. Implement a check against linkedinExpiresAt on each API call and trigger re-authentication when expired.
Pitfall Guide
- Legacy Scope Reliance: Using
r_liteprofileorr_emailaddresstriggers silent callback failures after LinkedIn's deprecation grace period. Always useopenid profile email. - NextAuth v5 Default Provider Misconfiguration: The built-in provider ships with legacy scopes and endpoints. Failing to override
issuer,userinfo, andauthorization.params.scoperesults in malformed token exchanges. - Incorrect Callback URL Path: LinkedIn strictly validates the registered callback. Missing
/api/auth/callback/linkedincauses opaque error pages that waste debugging time. - Wrong Identity Field Mapping: The OIDC
/v2/userinfoendpoint returnssubas the stable identifier. Mappingprofile.idreturnsundefinedand breaks user session persistence. - Scope/Permission Conflation: OIDC Sign-In only grants identity verification. Posting, feed reading, and community management require separate LinkedIn Developer Products, distinct scope sets, and manual approval queues.
- Aggressive Consent Caching: LinkedIn caches granted consents aggressively. Changing scopes in code without revoking the existing connection or passing
prompt=consentreturns tokens with outdated scope sets. - Ignoring Token Expiry Windows: Assuming indefinite token validity leads to
401 Unauthorizederrors after 60 days. Implement expiry validation againstexpires_atand handle re-authentication flows gracefully.
Deliverables
- OIDC Migration Blueprint: Architecture diagram detailing the Next.js 16 β NextAuth v5 β LinkedIn OIDC flow, including token lifecycle, JWT callback persistence, and consent state management.
- LinkedIn Developer Portal Checklist: Step-by-step verification list covering app creation, redirect URI registration, scope configuration, product approval queues, and environment variable mapping.
- Configuration Templates: Production-ready
.envschema,auth.tsprovider override template, and database migration script for storinglinkedinAccessTokenandlinkedinExpiresAtwith expiry indexing.
