codingstairs
노트에듀라이프연락
⌕검색⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

연락하기

로그인 없이도 보낼 수 있어요. 답변이 필요하면 이메일을 함께 적어 주세요.

  • 익명 폼으로 의견 남기기 →
  • ✉ warragon112@gmail.com
  • 카카오톡 오픈채팅 ↗

© 2026 codingstairs

  • 노트
  • 에듀
  • 검색
  • 라이프
  • 연락
  • 약관
  • RSS
  • GitHub
에듀›웹 보안의 기초 — JWT · OAuth · OWASP›3단계

3단계

OAuth + state · PKCE

0회 조회

OAuth + state · PKCE

소셜 로그인은 보안 구현을 외부 IdP 에 위임하는 패턴. 두 개의 작은 디테일 (state · PKCE) 을 빠뜨리면 CSRF · 토큰 탈취에 노출.

1. OAuth 2.0 흐름 요약

1. 사용자 → "카카오로 로그인" 클릭
2. 앱 → Kakao 인증 URL 로 redirect (client_id · redirect_uri · state 포함)
3. Kakao → 사용자 승인
4. Kakao → redirect_uri?code=xxx&state=xxx
5. 앱 (서버) → Kakao 토큰 엔드포인트에 code 교환 → access_token
6. 앱 → access_token 으로 사용자 정보 조회
7. 앱 → 자체 세션 쿠키 발급

1 ~ 4 는 브라우저 redirect · 5 ~ 7 은 서버 대 서버.

2. state — CSRF 방어

사용자가 알지 못하는 사이에 공격자 계정으로 로그인되는 "Login CSRF" 를 막습니다.

// /api/auth/kakao (initiate)
export async function GET(req: NextRequest) {
  const state = crypto.randomUUID();
  const url = new URL("https://kauth.kakao.com/oauth/authorize");
  url.searchParams.set("client_id", process.env.KAKAO_CLIENT_ID!);
  url.searchParams.set("redirect_uri", process.env.KAKAO_REDIRECT_URI!);
  url.searchParams.set("response_type", "code");
  url.searchParams.set("state", state);

  const res = NextResponse.redirect(url);
  res.cookies.set("oauth_state", state, {
    httpOnly: true, secure: true, sameSite: "lax", maxAge: 300,
  });
  return res;
}
// /api/auth/kakao/callback
export async function GET(req: NextRequest) {
  const code = req.nextUrl.searchParams.get("code");
  const queryState = req.nextUrl.searchParams.get("state");
  const cookieState = req.cookies.get("oauth_state")?.value;

  if (!code || !queryState || cookieState !== queryState) {
    return NextResponse.redirect(new URL("/login?e=csrf", req.url));
  }
  // ... code → token 교환
}

쿠키 state 와 query state 가 일치해야 진행.

3. PKCE — code intercept 방어

SPA · 모바일처럼 client_secret 을 안전하게 보관 못 하는 환경 에서 필수. 서버 기반 앱이어도 추가하면 안전.

import { createHash, randomBytes } from "crypto";

function base64url(b: Buffer) {
  return b.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

const codeVerifier = base64url(randomBytes(32));
const codeChallenge = base64url(createHash("sha256").update(codeVerifier).digest());

// 인증 URL 에 추가
url.searchParams.set("code_challenge", codeChallenge);
url.searchParams.set("code_challenge_method", "S256");

// 쿠키에 verifier 저장
res.cookies.set("oauth_verifier", codeVerifier, { httpOnly: true, maxAge: 300 });

callback 에서 token 교환 시 verifier 포함:

const body = new URLSearchParams({
  grant_type: "authorization_code",
  code, client_id, client_secret, redirect_uri,
  code_verifier: verifier,
});

4. Kakao · Naver 차이

provider state 필수 PKCE 지원
Kakao 권장 지원
Naver 필수 (공식 문서 명시) 지원
Google 권장 필수 (공식 권장)

Naver state 검증 누락은 계정 연결 공격 가능성.

5. redirect_uri 화이트리스트

Kakao · Naver 개발자 콘솔에서 정확한 URL 만 허용 등록. 와일드카드 금지.

https://example.com/api/auth/kakao/callback        ✅
https://example.com/*                              ❌ (일부 provider 허용하지만 피할 것)

6. 토큰 저장 위치

  • access_token (Kakao/Naver) — DB 에 저장 시 AES-256-GCM 암호화
  • 사용자 이메일 · ID — users 테이블 + auth_provider 컬럼
  • 세션 쿠키 — HTTP-only JWT (앞 장)

7. 계정 연결 · 이메일 중복

같은 이메일로 카카오 · 네이버 동시 가입 시?

  • 옵션 A — 첫 번째 provider 만 허용
  • 옵션 B — 동일 이메일은 자동 연결 (공격 벡터 존재)
  • 옵션 C — 사용자에게 "이미 가입됨, 다른 provider 로 로그인" 안내

A 가 가장 안전. 편의를 위해 B 를 하면 이메일 인증이 끝난 provider 만 연결.

8. 로그아웃

export async function GET(req: Request) {
  const res = NextResponse.redirect("/");
  res.cookies.delete("session");
  res.cookies.delete("refresh_token");
  // OAuth provider 로그아웃은 별도 (Kakao/Naver 공식 URL)
  return res;
}

앱 세션만 종료 vs provider 까지 로그아웃 — UX 정책 결정.

9. 자주 걸리는 자리

  • state 검증 생략 — Login CSRF 취약
  • redirect_uri 검증 허술 — open redirect
  • access_token 평문 저장 — DB 유출 시 타 서비스 계정 탈취
  • id_token 검증 생략 (OIDC) — 서명 검증 필수
  • localStorage 에 토큰 — XSS 에 고스란히 노출

10. 운영 체크리스트

  • 모든 provider state 검증
  • PKCE 도입 (가능한 provider)
  • redirect_uri 정확 매칭
  • access_token DB 저장 시 암호화
  • id_token · 서명 검증 (OIDC)

하고픈 말

OAuth 의 위험 요소는 대부분 state · redirect_uri · token 저장 세 자리에 집중. 이 셋만 정확하면 나머지는 provider SDK 가 처리.

Next

  • 04-input-validation

← 2단계

JWT · refresh · 회전

4단계 →

입력 검증 + 길이 상한