| name | oauth2-oidc-implementer |
| description | Implements OAuth 2.0 and OpenID Connect authentication flows with secure token handling and provider integration. Use when users request "OAuth setup", "OIDC implementation", "social login", "SSO integration", or "authentication flow". |
OAuth 2.0 & OIDC Implementer
Implement secure authentication with OAuth 2.0 and OpenID Connect.
Core Workflow
- Choose flow: Authorization Code, PKCE, Client Credentials
- Configure provider: Set up OAuth/OIDC provider
- Implement flow: Handle redirects and tokens
- Secure tokens: Storage and refresh
- Add providers: Multiple identity providers
- Handle sessions: Manage authenticated state
OAuth 2.0 Flows Overview
┌─────────────────────────────────────────────────────────────┐
│ OAuth 2.0 Flows │
├─────────────────────────────────────────────────────────────┤
│ Authorization Code + PKCE │ Web/Mobile apps (recommended) │
│ Client Credentials │ Machine-to-machine │
│ Device Code │ TV/IoT devices │
│ Implicit (deprecated) │ Do not use │
└─────────────────────────────────────────────────────────────┘
Authorization Code Flow with PKCE
Server Implementation (Next.js)
import { randomBytes, createHash } from 'crypto';
interface OAuthConfig {
clientId: string;
clientSecret: string;
authorizationUrl: string;
tokenUrl: string;
redirectUri: string;
scopes: string[];
}
const config: OAuthConfig = {
clientId: process.env.OAUTH_CLIENT_ID!,
clientSecret: process.env.OAUTH_CLIENT_SECRET!,
authorizationUrl: 'https://provider.com/oauth/authorize',
tokenUrl: 'https://provider.com/oauth/token',
redirectUri: process.env.OAUTH_REDIRECT_URI!,
scopes: ['openid', 'profile', 'email'],
};
function generateCodeVerifier(): string {
return randomBytes(32).toString('base64url');
}
function generateCodeChallenge(verifier: string): string {
return createHash('sha256').update(verifier).digest('base64url');
}
function generateState(): string {
return randomBytes(16).toString('hex');
}
export function getAuthorizationUrl(): {
url: string;
state: string;
codeVerifier: string;
} {
const state = generateState();
const codeVerifier = generateCodeVerifier();
const codeChallenge = generateCodeChallenge(codeVerifier);
const params = new URLSearchParams({
client_id: config.clientId,
redirect_uri: config.redirectUri,
response_type: 'code',
scope: config.scopes.join(' '),
state,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
});
return {
url: `${config.authorizationUrl}?${params}`,
state,
codeVerifier,
};
}
export async function exchangeCodeForTokens(
code: string,
codeVerifier: string
): Promise<TokenResponse> {
const response = await fetch(config.tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: config.clientId,
client_secret: config.clientSecret,
code,
redirect_uri: config.redirectUri,
code_verifier: codeVerifier,
}),
});
if (!response.ok) {
const error = await response.json();
throw new OAuthError(error.error_description || 'Token exchange failed');
}
return response.json();
}
interface TokenResponse {
access_token: string;
token_type: string;
expires_in: number;
refresh_token?: string;
id_token?: string;
scope: string;
}
Login Route
import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import { getAuthorizationUrl } from '@/lib/auth/oauth';
export async function GET() {
const { url, state, codeVerifier } = getAuthorizationUrl();
const cookieStore = cookies();
cookieStore.set('oauth_state', state, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 600,
path: '/',
});
cookieStore.set('code_verifier', codeVerifier, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 600,
path: '/',
});
return .(url);
}
Callback Route
import { NextRequest, NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import { exchangeCodeForTokens } from '@/lib/auth/oauth';
import { createSession } from '@/lib/auth/session';
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const code = searchParams.get('code');
const state = searchParams.get('state');
const error = searchParams.get('error');
if (error) {
const errorDescription = searchParams.get('error_description');
return NextResponse.redirect(
new URL(`/login?error=${encodeURIComponent(errorDescription || error)}`, request.url)
);
}
cookieStore = ();
storedState = cookieStore.()?.;
codeVerifier = cookieStore.()?.;
(!state || state !== storedState) {
.(
(, request.)
);
}
(!code || !codeVerifier) {
.(
(, request.)
);
}
{
tokens = (code, codeVerifier);
(tokens);
cookieStore.();
cookieStore.();
.( (, request.));
} (error) {
.(, error);
.(
(, request.)
);
}
}
OpenID Connect Integration
OIDC Discovery
interface OIDCConfig {
issuer: string;
authorization_endpoint: string;
token_endpoint: string;
userinfo_endpoint: string;
jwks_uri: string;
scopes_supported: string[];
response_types_supported: string[];
}
let cachedConfig: OIDCConfig | null = null;
export async function discoverOIDCConfig(issuer: string): Promise<OIDCConfig> {
if (cachedConfig) return cachedConfig;
const response = await fetch(`${issuer}/.well-known/openid-configuration`);
if (!response.ok) {
throw new Error('Failed to fetch OIDC configuration');
}
cachedConfig = await response.json();
return cachedConfig;
}
ID Token Validation
import { createRemoteJWKSet, jwtVerify } from 'jose';
import { discoverOIDCConfig } from './oidc';
interface IDTokenClaims {
iss: string;
sub: string;
aud: string | string[];
exp: number;
iat: number;
nonce?: string;
email?: string;
email_verified?: boolean;
name?: string;
picture?: string;
}
let jwks: ReturnType<typeof createRemoteJWKSet> | null = null;
export async function verifyIdToken(
idToken: string,
expectedNonce?: string
): Promise<IDTokenClaims> {
const config = await discoverOIDCConfig(process.env.OIDC_ISSUER!);
if (!jwks) {
jwks = ( (config.));
}
{ payload } = (idToken, jwks, {
: config.,
: process..!,
});
(expectedNonce && payload. !== expectedNonce) {
();
}
payload ;
}
User Info Endpoint
interface UserInfo {
sub: string;
email?: string;
email_verified?: boolean;
name?: string;
given_name?: string;
family_name?: string;
picture?: string;
locale?: string;
}
export async function fetchUserInfo(accessToken: string): Promise<UserInfo> {
const config = await discoverOIDCConfig(process.env.OIDC_ISSUER!);
const response = await fetch(config.userinfo_endpoint, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new Error('Failed to fetch user info');
}
return response.json();
}
Session Management
import { SignJWT, jwtVerify } from 'jose';
import { cookies } from 'next/headers';
const SESSION_SECRET = new TextEncoder().encode(process.env.SESSION_SECRET!);
interface Session {
userId: string;
email: string;
accessToken: string;
refreshToken?: string;
expiresAt: number;
}
export async function createSession(tokens: TokenResponse): Promise<void> {
const claims = await verifyIdToken(tokens.id_token!);
const session: Session = {
userId: claims.sub,
email: claims.email!,
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresAt: .() + tokens. * ,
};
jwt = (session)
.({ : })
.()
.();
().(, jwt, {
: ,
: process.. === ,
: ,
: * * * ,
: ,
});
}
(): < | > {
sessionCookie = ().()?.;
(!sessionCookie) ;
{
{ payload } = (sessionCookie, );
payload ;
} {
;
}
}
(): < | > {
session = ();
(!session?.) ;
(session. > .() + ) {
session;
}
tokens = (session.);
(tokens);
();
}
(): <> {
response = (config., {
: ,
: { : },
: ({
: ,
: config.,
: config.,
: refreshToken,
}),
});
(!response.) {
();
}
response.();
}
Multiple Providers
interface OAuthProvider {
id: string;
name: string;
authorizationUrl: string;
tokenUrl: string;
userInfoUrl: string;
clientId: string;
clientSecret: string;
scopes: string[];
mapUserInfo: (data: any) => UserProfile;
}
export const providers: Record<string, OAuthProvider> = {
google: {
id: 'google',
name: 'Google',
authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
userInfoUrl: 'https://www.googleapis.com/oauth2/v3/userinfo',
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
scopes: ['openid', 'email', 'profile'],
: ({
: data.,
: data.,
: data.,
: data.,
}),
},
: {
: ,
: ,
: ,
: ,
: ,
: process..!,
: process..!,
: [, ],
: ({
: (data.),
: data.,
: data. || data.,
: data.,
}),
},
: {
: ,
: ,
: ,
: ,
: ,
: process..!,
: process..!,
: [, , , ],
: ({
: data.,
: data. || data.,
: data.,
: ,
}),
},
};
Client Credentials Flow
interface ClientCredentialsConfig {
tokenUrl: string;
clientId: string;
clientSecret: string;
scopes: string[];
}
let cachedToken: { token: string; expiresAt: number } | null = null;
export async function getMachineToken(config: ClientCredentialsConfig): Promise<string> {
if (cachedToken && cachedToken.expiresAt > Date.now() + 60000) {
return cachedToken.token;
}
const response = await fetch(config.tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(
`${config.clientId}:${config.clientSecret}`
).toString()}`,
},
: ({
: ,
: config..(),
}),
});
(!response.) {
();
}
data = response.();
cachedToken = {
: data.,
: .() + data. * ,
};
cachedToken.;
}
Best Practices
- Always use PKCE: Even for confidential clients
- Validate state: Prevent CSRF attacks
- Verify tokens: Check signature and claims
- Secure storage: HttpOnly cookies for tokens
- Refresh proactively: Before expiration
- Handle errors gracefully: Clear messaging
- Use HTTPS: Always in production
- Limit scopes: Request minimum needed
Output Checklist
Every OAuth/OIDC implementation should include: