[{"data":1,"prerenderedAt":180},["ShallowReactive",2],{"blog-blueprints/oauth-integrations":3},{"id":4,"title":5,"body":6,"category":160,"date":161,"dateModified":161,"description":162,"draft":163,"extension":164,"faq":165,"featured":163,"headerVariant":166,"image":165,"keywords":165,"meta":167,"navigation":168,"ogDescription":169,"ogTitle":165,"path":170,"readTime":171,"schemaOrg":172,"schemaType":173,"seo":174,"sitemap":175,"stem":176,"tags":177,"twitterCard":178,"__hash__":179},"blog/blog/blueprints/oauth-integrations.md","OAuth Integration Security Guide",{"type":7,"value":8,"toc":149},"minimark",[9,20,24,30,35,50,54,63,67,76,85,89,94,97,100,103,106,109,112,123,137],[10,11,12],"blueprint-summary",{},[13,14,15,19],"p",{},[16,17,18],"strong",{},"To secure OAuth integrations,"," you need to: (1) always implement PKCE flow even for server-side applications, (2) validate state parameters to prevent CSRF attacks, (3) exchange authorization codes server-side only where client secrets are secure, (4) verify ID tokens using the provider's JWKS before trusting claims, and (5) explicitly configure redirect URIs in your OAuth provider. This blueprint covers secure OAuth patterns for any provider integration.",[21,22],"blueprint-meta",{"time":23},"2-3 hours",[25,26,27],"tldr",{},[13,28,29],{},"Always use PKCE (even for server-side apps), validate state parameters to prevent CSRF, exchange codes server-side only, and verify tokens before trusting their contents. Use established libraries instead of implementing OAuth from scratch.",[31,32,34],"h2",{"id":33},"pkce-flow-implementation-oauth-20","PKCE Flow Implementation OAuth 2.0",[36,37,39],"code-block",{"label":38},"lib/oauth.ts",[40,41,46],"pre",{"className":42,"code":44,"language":45},[43],"language-text","import crypto from 'crypto'\n\nfunction generateCodeVerifier(): string {\n  return crypto.randomBytes(32).toString('base64url')\n}\n\nfunction generateCodeChallenge(verifier: string): string {\n  return crypto.createHash('sha256').update(verifier).digest('base64url')\n}\n\nexport async function initiateOAuth(provider: 'github' | 'google') {\n  const state = crypto.randomBytes(16).toString('hex')\n  const codeVerifier = generateCodeVerifier()\n  const codeChallenge = generateCodeChallenge(codeVerifier)\n\n  // Store in session/cookie (encrypted)\n  await storeOAuthState({ state, codeVerifier, provider })\n\n  const params = new URLSearchParams({\n    client_id: process.env[`${provider.toUpperCase()}_CLIENT_ID`]!,\n    redirect_uri: `${process.env.BASE_URL}/api/auth/callback/${provider}`,\n    scope: 'openid profile email',\n    response_type: 'code',\n    state,\n    code_challenge: codeChallenge,\n    code_challenge_method: 'S256',\n  })\n\n  return `https://provider.example.com/authorize?${params}`\n}\n","text",[47,48,44],"code",{"__ignoreMap":49},"",[31,51,53],{"id":52},"callback-handler","Callback Handler",[36,55,57],{"label":56},"app/api/auth/callback/[provider]/route.ts",[40,58,61],{"className":59,"code":60,"language":45},[43],"export async function GET(\n  req: Request,\n  { params }: { params: { provider: string } }\n) {\n  const url = new URL(req.url)\n  const code = url.searchParams.get('code')\n  const state = url.searchParams.get('state')\n  const error = url.searchParams.get('error')\n\n  if (error) {\n    return Response.redirect('/login?error=oauth_failed')\n  }\n\n  // Retrieve stored state\n  const storedState = await getOAuthState()\n\n  // CRITICAL: Validate state to prevent CSRF\n  if (!storedState || storedState.state !== state) {\n    return Response.redirect('/login?error=invalid_state')\n  }\n\n  // Exchange code for tokens (server-side only!)\n  const tokenResponse = await fetch('https://provider.example.com/token', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n    body: new URLSearchParams({\n      grant_type: 'authorization_code',\n      code: code!,\n      redirect_uri: `${process.env.BASE_URL}/api/auth/callback/${params.provider}`,\n      client_id: process.env.CLIENT_ID!,\n      client_secret: process.env.CLIENT_SECRET!,\n      code_verifier: storedState.codeVerifier,  // PKCE\n    }),\n  })\n\n  const tokens = await tokenResponse.json()\n\n  // Verify ID token before trusting it\n  const user = await verifyIdToken(tokens.id_token)\n\n  // Create session\n  await createSession(user)\n\n  return Response.redirect('/dashboard')\n}\n",[47,62,60],{"__ignoreMap":49},[31,64,66],{"id":65},"token-verification-oauth-20","Token Verification OAuth 2.0",[36,68,70],{"label":69},"lib/verify-token.ts",[40,71,74],{"className":72,"code":73,"language":45},[43],"import * as jose from 'jose'\n\nexport async function verifyIdToken(idToken: string) {\n  // Fetch provider's public keys\n  const JWKS = jose.createRemoteJWKSet(\n    new URL('https://provider.example.com/.well-known/jwks.json')\n  )\n\n  try {\n    const { payload } = await jose.jwtVerify(idToken, JWKS, {\n      issuer: 'https://provider.example.com',\n      audience: process.env.CLIENT_ID!,\n    })\n\n    // Verify required claims\n    if (!payload.sub || !payload.email) {\n      throw new Error('Missing required claims')\n    }\n\n    return {\n      id: payload.sub,\n      email: payload.email as string,\n      name: payload.name as string,\n    }\n  } catch (error) {\n    throw new Error('Invalid ID token')\n  }\n}\n",[47,75,73],{"__ignoreMap":49},[77,78,79],"warning-box",{},[13,80,81,84],{},[16,82,83],{},"Never exchange codes client-side."," The authorization code must be exchanged for tokens on your server, where the client secret is secure. PKCE adds protection but doesn't replace server-side exchange.",[31,86,88],{"id":87},"security-checklist","Security Checklist",[90,91,93],"h4",{"id":92},"pre-launch-checklist","Pre-Launch Checklist",[13,95,96],{},"PKCE implemented (code_challenge)",[13,98,99],{},"State parameter validated",[13,101,102],{},"Code exchange server-side only",[13,104,105],{},"ID tokens verified before use",[13,107,108],{},"Redirect URIs explicitly configured",[13,110,111],{},"Client secret stored securely",[113,114,115,120],"stack-comparison",{},[116,117,119],"h3",{"id":118},"related-integration-stacks","Related Integration Stacks",[13,121,122],{},"Auth0 Managed OAuth\nClerk OAuth Integration\nNextAuth Self-Hosted OAuth",[124,125,126,132],"related-articles",{},[127,128],"related-card",{"description":129,"href":130,"title":131},"Managed OAuth","/blog/blueprints/auth0-nextjs","Auth0 + Next.js",[127,133],{"description":134,"href":135,"title":136},"Self-hosted OAuth","/blog/blueprints/nextauth-prisma","NextAuth + Prisma",[138,139,142,146],"cta-box",{"href":140,"label":141},"/","Start Free Scan",[31,143,145],{"id":144},"check-your-oauth-implementation","Check Your OAuth Implementation",[13,147,148],{},"Scan for OAuth vulnerabilities.",{"title":49,"searchDepth":150,"depth":150,"links":151},2,[152,153,154,155,159],{"id":33,"depth":150,"text":34},{"id":52,"depth":150,"text":53},{"id":65,"depth":150,"text":66},{"id":87,"depth":150,"text":88,"children":156},[157],{"id":118,"depth":158,"text":119},3,{"id":144,"depth":150,"text":145},"blueprints","2026-02-09","Security guide for OAuth provider integrations. Implement PKCE flow, validate state parameters, handle tokens securely, and protect against common OAuth vulnerabilities.",false,"md",null,"purple",{},true,"Secure OAuth provider integration patterns.","/blog/blueprints/oauth-integrations","12 min read","[object Object]","Article",{"title":5,"description":162},{"loc":170},"blog/blueprints/oauth-integrations",[],"summary_large_image","7prTUAti-8I3pZlhGn_o13NS7KQWZofs9R2i-9eIzBM",1775843932169]