codingstairs
NotesEDULifeContact
⌕Search⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

Get in touch

Send without signing in. Add your email if you'd like a reply.

  • Leave a message anonymously →
  • ✉ warragon112@gmail.com
  • KakaoTalk Open Chat ↗

© 2026 codingstairs

  • Notes
  • EDU
  • Search
  • Life
  • Contact
  • Legal
  • RSS
  • GitHub
EDU›Web security foundations — JWT · OAuth · OWASP›Step 3

Step 3

OAuth + state · PKCE

0 views

OAuth + state · PKCE

Social login delegates identity to an IdP. Two small details (state · PKCE) prevent CSRF and code interception.

1. Flow

1. User clicks "Sign in with Kakao"
2. App → redirect to Kakao (client_id · redirect_uri · state)
3. Kakao → user approves
4. Kakao → redirect_uri?code=xxx&state=xxx
5. App (server) → exchange code for access_token
6. App → fetch user info
7. App → issue session cookie

2. state — CSRF defense

// initiate
const state = crypto.randomUUID();
res.cookies.set("oauth_state", state, { httpOnly: true, secure: true, sameSite: "lax", maxAge: 300 });
url.searchParams.set("state", state);
// callback
const queryState = req.nextUrl.searchParams.get("state");
const cookieState = req.cookies.get("oauth_state")?.value;
if (cookieState !== queryState) return redirect("/login?e=csrf");

3. PKCE

Required for SPAs / mobile (no secure client_secret), recommended everywhere.

const codeVerifier = base64url(randomBytes(32));
const codeChallenge = base64url(createHash("sha256").update(codeVerifier).digest());
url.searchParams.set("code_challenge", codeChallenge);
url.searchParams.set("code_challenge_method", "S256");
res.cookies.set("oauth_verifier", codeVerifier, { httpOnly: true, maxAge: 300 });

At token exchange include the verifier.

4. Per provider

Provider state PKCE
Kakao recommended supported
Naver required supported
Google recommended required

5. Redirect URI whitelist

Register exact URLs in the provider console. No wildcards.

6. Token storage

  • access_token — encrypt at rest (AES-256-GCM) if stored in DB
  • email · id — users table with auth_provider
  • session cookie — HTTP-only JWT

7. Account linking / duplicates

  • A — only the first provider
  • B — auto-link same email (vector for takeover)
  • C — ask user to log in via the existing provider

A is safest.

8. Logout

res.cookies.delete("session");
res.cookies.delete("refresh_token");

App session only; provider-level logout is a separate UX decision.

9. Gotchas

  • No state check → login CSRF
  • Lax redirect_uri → open redirect
  • Plaintext access_token → neighbour-service takeover
  • id_token signature skipped (OIDC)
  • Tokens in localStorage → XSS takes them

10. Checklist

  • state verified
  • PKCE where supported
  • exact redirect_uri
  • encrypted access_token
  • id_token signature verified

Closing

Most OAuth risk sits in state, redirect_uri, and token storage. Get these three right and the provider SDK handles the rest.

Next

  • 04-input-validation

← Step 2

JWT · refresh · rotation

Step 4 →

Input validation + length caps