본문으로 바로가기

OAuth — state · PKCE · OIDC

OAuth 2.0 은 사용자의 자격을 노출하지 않고 다른 시스템에 권한 위임을 허용하는 표준. 위에 OpenID Connect 가 인증 (누구인가) 을 얹습니다. 이 글은 Authorization Code Flow + PKCE · state 와 nonce · 스코프 설계 · Google · Kakao · Naver ·…

5회 조회약 6분 읽기
X에 공유 새 창에서 열림
목차

OAuth 2.0 은 사용자의 자격을 노출하지 않고 다른 시스템에 권한 위임을 허용하는 표준. 위에 OpenID Connect 가 인증 (누구인가) 을 얹습니다. 이 글은 Authorization Code Flow + PKCE · state 와 nonce · 스코프 설계 · Google · Kakao · Naver · GitHub OAuth 의 사실 차이.

1. 표준에 대한 이야기

  • OAuth 2.0 — RFC 6749 (2012). 권한 위임의 프레임워크. 자격 (비밀번호) 을 직접 공유하지 않고 토큰으로 권한을 부여.
  • OpenID Connect (OIDC) — 2014. OAuth 2.0 위에 ID Token 과 사용자 정보 엔드포인트를 더해 인증을 표준화. ID Token 은 JWT.
  • PKCE — RFC 7636 (2015). 공개 클라이언트 (SPA · 모바일) 의 인증 코드 가로채기를 차단하기 위한 확장.
  • OAuth 2.1 — 2026년에도 Internet-Draft인 통합 사양. 현재 배포 판단은 초안만 기다리지 말고 2025년의 OAuth 2.0 Security BCP(RFC 9700)를 함께 기준으로 삼음.

2. Authorization Code + PKCE 흐름

  1. 클라이언트가 code_verifier (랜덤 문자열) 생성. code_challenge = base64url(sha256(verifier)).
  2. /authorize?response_type=code&client_id=...&redirect_uri=...&scope=...&state=...&code_challenge=...&code_challenge_method=S256 으로 리다이렉트.
  3. 사용자 동의.
  4. IdP 가 redirect_uri?code=...&state=... 로 돌려보냄.
  5. 클라이언트가 state 를 이전 값과 비교 (CSRF 검증).
  6. 클라이언트가 /tokencode + code_verifier 제출.
  7. IdP 가 code_challengecode_verifier 의 일치를 확인 후 토큰 발급.

3. state 파라미터

state 는 CSRF 방지가 핵심. 공격자가 자기 인증 코드를 피해자에게 리다이렉트해 피해자가 공격자 계정으로 로그인하는 시나리오 (로그인 CSRF) 를 막음.

  • 클라이언트가 임의의 nonce 같은 값을 만들어 세션에 저장.
  • /authorizestate 로 전달.
  • 콜백에서 받은 state 가 세션의 값과 같은지 확인.
  • 다르면 거부.

state 는 충돌 가능성이 낮은 충분히 긴 랜덤 값.

4. nonce (OIDC)

OIDC 의 ID Token 안에 포함되는 값. ID Token 의 재사용 (replay) 방지가 목적.

  • 클라이언트가 인증 요청에 nonce 보냄.
  • IdP 가 받은 nonce 를 ID Token 클레임으로 돌려줌.
  • 클라이언트가 자기 nonce 와 일치하는지 검증.

state 와 nonce 는 다른 목적으로 둘 다 필요합니다.

5. PKCE 의 의미

코드 가로채기 공격은 모바일 · SPA 에서 클라이언트 시크릿이 안전하게 보관되지 않는 문제에서 출발. PKCE 는 클라이언트 시크릿 없이도 같은 출처의 클라이언트만 토큰을 받을 수 있게 — 코드를 가로채도 code_verifier 가 없으면 토큰 교환이 실패.

OAuth 2.0 에서 SPA · 모바일에 옵션이었으나, OAuth 2.1 에서 모든 클라이언트에 사실상 필수.

공격과 방어의 매핑

공격·실수 방어 값 callback 검증 실패 행동
로그인 CSRF state 또는 PKCE binding 세션·transaction과 일치 token 교환 전 거부
authorization code 탈취 PKCE S256 verifier로 code 교환 재사용 없이 실패
ID Token replay OIDC nonce ID Token claim 일치 모든 token 폐기
다른 client용 token 혼용 iss·aud issuer·client ID 정확히 일치 세션 생성 금지
callback 중복 실행 1회용 transaction 사용 여부 원자 갱신 두 번째 요청 거부
authorize 시작 → transaction(state, nonce, verifier) 저장
callback → state 확인 → code+verifier 교환 → 서명/iss/aud/exp/nonce 확인 → transaction 소모 → session

검증 순서를 바꾸어 ID Token 확인 전에 세션을 만들거나, 실패한 callback의 token 일부를 사용하면 안 됩니다. 여러 탭을 지원하려면 state 하나를 덮어쓰지 말고 transaction별 만료·1회 사용 상태를 저장합니다.

6. 폐기 권고된 흐름

Implicit Flowresponse_type=token 으로 액세스 토큰을 URL 프래그먼트로 직접 받음. 코드 단계 없음 → 토큰이 브라우저 히스토리 · 로그에 노출 위험. 현재는 권장 안 됨, OAuth 2.1 초안에서 제거.

Resource Owner Password Credentials (ROPC) — 사용자가 비밀번호를 클라이언트에 직접 입력. OAuth 의 핵심 동기 (비밀번호를 공유하지 않음) 에 어긋남. 폐기 권고.

Device Authorization Grant — RFC 8628 (2019). TV · IoT 등 입력이 제한된 기기에서 별도 디바이스로 인증을 진행하는 흐름. device_code · user_code 가 핵심.

7. 스코프 설계

  • 표준 OIDC 스코프openid · profile · email · address · phone.
  • 공급자별 — Google https://www.googleapis.com/auth/calendar.readonly 처럼 URL 형식이 흔함.
  • 자체 API — 자기 도메인 + 권한 이름 (api.read:posts).

권고:

  • 최소 권한 원칙. 처음에 다 요구하지 말고 필요할 때 incremental authorization.
  • 사용자에게 동의 화면이 명료하도록.
  • 서버에서 다시 검증. 클라이언트 토큰의 스코프를 그대로 신뢰하지 않음.

8. 공급자별 사실 차이

Google — OIDC 표준 충실. discovery 문서 https://accounts.google.com/.well-known/openid-configuration. ID Token RS256, JWKS 회전. incremental authorization 가능. consent 가 첫 동의 후 재요청 시 생략 가능 (prompt=consent 로 강제).

Kakao — OAuth 2.0 기반. OIDC 도 지원 (2022 년경 추가, ID Token 발급). Kakao Developers 콘솔에서 동의 항목을 별도로 설정해야 사용자에게 표시. 일부 정보 (이메일 등) 는 사용자가 비공개 처리하면 비어 올 수 있음.

Naver — OAuth 2.0. OIDC 표준 ID Token 은 미제공. 사용자 정보 API (/v1/nid/me) 로 별도 조회. 한국 사용자를 대상으로 하는 자리에서 자주.

GitHub — OAuth 2.0 (OIDC 는 GitHub Actions OIDC 등 한정 자리). 일반 로그인은 OIDC 가 아닌 OAuth 2.0. 사용자 정보는 GET /user. 스코프가 단순 (read:user · repo) 하지만 강력 — repo 는 모든 저장소 접근.

Apple Sign in — OIDC 표준. ID Token 사용. 이메일을 사용자가 가릴 수 있음 (private relay). 첫 로그인 응답에만 이름이 포함되는 점이 알려져 있음 — 백엔드가 그때 저장해야.

9. 안전한 디폴트

  • 인증 흐름은 Authorization Code + PKCE 하나.
  • redirect_uri 는 사전 등록된 정확한 값만 (와일드카드 위험).
  • state · nonce 검증 빠뜨리지 않기.
  • access · refresh 분리, 짧은 access TTL (01-jwt-rotation).
  • 토큰 저장은 httpOnly 쿠키 또는 안전한 키스토어.
  • 백엔드는 서명된 ID Token 의 클레임 검증 — iss · aud · exp · nonce.

10. 자주 걸리는 자리

state 누락 · 고정값 — CSRF 차단이 사실상 사라짐. 매 요청 새 값.

redirect_uri 의 느슨함 — 와일드카드 · prefix 매칭은 위험. 정확한 매칭만.

public client 가 시크릿을 가짐 — 모바일 · SPA 의 클라이언트 시크릿은 결국 노출. PKCE 로 대체.

OIDC 의 aud 검증 누락 — 다른 클라이언트로 발급된 ID Token 을 그대로 받아들이면 토큰 혼용 사고.

사용자 정보 API 응답 변동 — 공급자가 응답 키 이름 · null 처리를 변경할 수 있음. 방어 코드.

이메일 신뢰 — GitHub 같은 공급자의 이메일이 미인증일 수 있음. 자체 이메일 검증 흐름 별도.

로그아웃의 의미 — 우리 세션을 끊는 것과 IdP 세션을 끊는 것은 다름. RP-Initiated Logout 표준은 OIDC 의 별도 사양.

콜백의 동시 실행 — 사용자가 여러 탭에서 OAuth 를 시작하면 state 가 섞임. 상태 저장 키 분리.

하고픈 말

OAuth 2.0 + PKCE + state + nonce 의 표준 흐름이 안전한 디폴트의 토대입니다. 여기서 더 나은 자리는 OIDC 의 ID Token 검증 (iss · aud · exp · nonce) 까지 내려가는 것. 공급자별 사소한 차이 (Naver 의 OIDC 미제공, GitHub 의 미인증 이메일) 가 실수의 자리이니 자체 이메일 검증 흐름이 보조로 자주 함께 따라옵니다.

Next

  • rate-limit-redis
  • input-validation-zod

RFC 6749 OAuth 2.0 · RFC 7636 PKCE · RFC 9700 OAuth 2.0 BCP · OpenID Connect Core · Google Identity · Kakao 로그인 · Naver 로그인 API · GitHub OAuth 를 참고합니다.

security 카테고리의 다른 글

카테고리 전체 보기 →

관련 글

이 글이 도움이 되었나요?