OAuth 2.0 Implementation Guide for Developers
OAuth 2.0 Implementation Guide for Developers
OAuth 2.0 lets users grant your application access to their data on other services without sharing their passwords. Understanding the flows, PKCE, and token management is essential for secure implementations.
Core Concepts
Authorization Code Flow with PKCE
This is the recommended flow for all clients — web apps, SPAs, and mobile apps. PKCE (Proof Key for Code Exchange) prevents authorization code interception.
Step 1: Generate PKCE Codes
function generateCodeVerifier(): string {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return btoa(String.fromCharCode(...array))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
async function generateCodeChallenge(verifier: string): Promise {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest("SHA-256", data);
return btoa(String.fromCharCode(...new Uint8Array(hash)))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
Step 2: Redirect to Authorization Server
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store codeVerifier in session — you need it later
sessionStorage.setItem("code_verifier", codeVerifier);
const params = new URLSearchParams({
client_id: "your-client-id",
redirect_uri: "https://yourapp.com/callback",
response_type: "code",
scope: "openid profile email",
state: crypto.randomUUID(), // CSRF protection
code_challenge: codeChallenge,
code_challenge_method: "S256",
});
window.location.href = https://auth.provider.com/authorize?\${params};
Step 3: Exchange Code for Tokens
// app/api/auth/callback/route.ts
export async function GET(request: Request) {
const url = new URL(request.url);
const code = url.searchParams.get("code");
const codeVerifier = getFromSession("code_verifier");
const tokenResponse = await fetch("https://auth.provider.com/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code: code!,
redirect_uri: "https://yourapp.com/callback",
client_id: "your-client-id",
code_verifier: codeVerifier,
}),
});
const tokens = await tokenResponse.json();
// tokens: { access_token, refresh_token, id_token, expires_in }
}
Token Refresh
Access tokens expire. Use the refresh token to get new ones without user interaction:
async function refreshAccessToken(refreshToken: string) {
const response = await fetch("https://auth.provider.com/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: "your-client-id",
}),
});
if (!response.ok) {
// Refresh token is invalid — user must re-authenticate
throw new Error("Session expired");
}
return response.json();
}
Common Pitfalls
expires_in and refresh proactively.Security Checklist
state parameter matches what you sent.Conclusion
OAuth 2.0 with PKCE is the standard for secure authentication and authorization. Use the authorization code flow for all clients, store tokens securely, implement refresh logic, and validate every parameter. Most security issues come from skipping these fundamentals.