본문으로 건너뛰기
yutils

OAuth 2.0 정리 — Authorization Code·PKCE·Client Credentials, 언제 무엇을 쓸까

4 가지 흐름의 차이, implicit 가 PKCE 로 대체된 이유, refresh token 과 scope, OpenID Connect 와의 관계.

약 10분 읽기

"Google 로 로그인" 버튼 뒤에 OAuth 2.0 이 있다. 이 가이드는 OAuth 가 실제로 무엇인지 (인증이 아니라 위임), 4 가지 표준 흐름의 차이, 왜 implicit 흐름이 PKCE 로 대체됐는지, refresh token 과 scope 의 의미를 정리한다. RFC 6749 + RFC 7636 (PKCE) + 현재 best practice.

OAuth 가 푸는 문제

이메일 마케팅 도구가 사용자의 Gmail 연락처를 가져오고 싶다고 하자. 예전 방식은 사용자 비밀번호를 직접 받아 Google 에 로그인하는 것 — 끔찍 하다. 비밀번호가 제 3 자에게 노출되고, 모든 권한을 다 가짐.

OAuth 의 답:

  1. 제 3 자 (마케팅 도구) 가 사용자에게 "Google 권한 받아 와" 요청.
  2. 사용자는 Google 로 가서 로그인 + "연락처 read 만" 동의.
  3. Google 이 마케팅 도구에 access token 발급.
  4. 마케팅 도구는 token 으로 연락처 API 호출. 비밀번호는 모름.

핵심 키워드: 위임 (delegation). 사용자가 자기 권한 일부 를 제 3 자에게 위임. OAuth 자체는 인증 (사용자가 누구인지 확인) 이 아니다. 인증이 필요하면 OpenID Connect (OAuth 위에 정체성 정보 추가) 를 쓴다.

네 가지 역할

  • Resource Owner — 사용자 (데이터의 주인).
  • Client — 데이터를 빌리려는 앱 (마케팅 도구).
  • Authorization Server — 권한을 발급하는 곳 (Google OAuth 서버).
  • Resource Server — 보호된 API (Google Contacts API).

Google·GitHub·Auth0 같은 IdP 는 Authorization Server + Resource Server 역할을 동시에 한다.

4 가지 표준 Grant Type

1. Authorization Code (권장 — 서버 사이드 앱)

가장 흔한 흐름. 백엔드가 있는 웹 앱.

  1. Client → 사용자를 Auth Server 의 /authorize 로 보냄. query: response_type=code, client_id, redirect_uri, scope, state.
  2. 사용자 동의 후 Auth Server 가 redirect_uri?code=...&state=... 로 다시 보냄.
  3. Client (서버) 가 /token 에 POST — code + client_secret 교환 → access token + (옵션) refresh token.
  4. access token 으로 API 호출 (Authorization: Bearer ...).

장점: client_secret 이 서버에만 있어 노출 0. token 이 브라 우저 URL 에 절대 안 나타남.

state 파라미터는 CSRF 방어 — 랜덤 값 발급 + 콜백 시 검증. URL 파서 로 callback URL 의 query 를 분해해 보면 흐름이 직관적.

2. Authorization Code + PKCE (권장 — SPA·모바일)

SPA·모바일은 client_secret 을 안전하게 보관 못 한다 (코드에 박히면 누구나 추출). PKCE (Proof Key for Code Exchange, RFC 7636) 가 해결.

  1. Client 가 code_verifier (43-128 자 랜덤) 를 만들고 code_challenge = SHA256(code_verifier) 계산.
  2. /authorize 호출 시 code_challenge + code_challenge_method=S256 함께 전송.
  3. callback 후 /token 호출 시 code_verifier (원본) 를 함께 보냄. Auth Server 가 SHA256 다시 계산해 일치 확인.

효과: 공격자가 code 를 가로채도 code_verifier 없으면 token 교환 불가. client_secret 없이도 안전한 코드 교환. SHA-256 계산을 직접 보고 싶다면 SHA 해시 에 verifier 를 넣어 결과를 challenge 와 비교할 수 있다.

3. Client Credentials (server-to-server)

사용자 개입 없는 흐름. Cron job 이 자체 권한으로 API 호출. Client 가 직접 /tokengrant_type=client_credentials + client_id + client_secret 보내고 access token 받음.

스코프는 클라이언트에 미리 부여된 권한 안에서만. 사용자 데이터가 아니라 애플리케이션 자체의 데이터에 접근할 때 (예: 자체 분석 데이터, admin API).

4. Refresh Token

엄밀히 grant type 이 아니라 access token 갱신용 별도 grant.

POST /token
grant_type=refresh_token
refresh_token=<refresh_token>
client_id=...

access token 은 짧게 (5~60 분), refresh 는 길게 (수일~수주). access 가 만료되면 refresh 로 새로 받음. refresh 자체가 노출되면 더 위험하므로 보안 저장소 (HttpOnly Secure 쿠키, OS Keychain 등) 에 보관.

(deprecated) Implicit / Resource Owner Password

둘 다 OAuth 2.1 draft 에서 제거. Implicit 은 token 이 URL fragment 로 직접 노출 → CSRF/XSS 위험. Password Grant 는 사용자 비밀번호를 client 가 받음 → OAuth 의 본 취지 위반. 새 시스템에서 쓰지 말 것.

Scope — 권한 단위

scope 파라미터로 위임 범위를 명시. 공급자마다 다름:

  • Google: https://www.googleapis.com/auth/contacts.readonly
  • GitHub: repo, user:email, workflow
  • OpenID Connect: openid profile email

원칙: 최소 권한. read 만 필요하면 read scope 만 요청. 사용자 동의 화면이 짧을수록 conversion 도 높다.

Access Token — 두 가지 형식

JWT (self-contained)

토큰 자체에 사용자 정보 + 권한 박힘. Resource Server 는 JWT 디코더 같은 라이브러리로 검증만 하면 됨. DB 조회 0. Auth0, Cognito, Firebase 등이 사용.

직접 발급해 보고 싶다면 JWT 생성기 (HMAC) 로 payload + 시크릿 + alg 입력해 토큰을 만들 수 있다. HS256 의 서명은 HMAC 생성기 의 결과와 동일.

Opaque (introspection)

토큰이 그냥 랜덤 문자열. Resource Server 가 매 요청마다 Auth Server 의 /introspect 엔드포인트에 물어봄. Auth Server 가 즉시 회수 가능 (장점). 매번 조회 비용 (단점).

OpenID Connect — OAuth + 정체성

OAuth 는 권한 위임만. "사용자가 누구인지" 알려면 OIDC.

  • scope=openid 가 OIDC 활성화.
  • access token 외에 ID token (JWT) 추가 발급. 사용자 ID·이메일·이름 등 표준 claim 박힘.
  • /userinfo 엔드포인트로 추가 사용자 정보 조회 가능.

"Sign in with Google" 의 정체는 OIDC. Google 의 ID token 을 받아 우리 앱의 사용자 세션을 만든다.

흔한 함정

1. state 검증 누락

callback 에서 state 를 검증 안 하면 CSRF — 공격자가 자신의 code 를 사용자에게 redirect 시켜 사용자 계정에 자기 OAuth 를 연결. 반드시 발급 시 저장 + 콜백 시 일치 확인.

2. PKCE 없는 SPA

client_secret 을 SPA 에 박는 건 즉시 노출. code_verifier 가 동적 생성되므로 안전. 모든 SPA 는 PKCE 강제.

3. token 을 localStorage 에 저장

XSS 한 번이면 모든 token 탈취. HttpOnly Secure 쿠키 권장. 또는 메모리 + refresh 만 쿠키 패턴.

4. redirect_uri 와일드카드

Auth Server 에 redirect_uri 정확히 등록. example.com/* 같은 와일드카드는 open redirect → token 탈취 취약.

5. scope 과다 요청

"그냥 다 받아두자" → 동의 화면이 무서워 보이고 사용자 이탈. 필요한 scope 만 단계적으로 요청.

6. refresh token rotation 미적용

refresh token 사용 시 새 refresh + 이전 token 무효화 패턴 권장 (RFC 6749 § 6 의 sender-constrained / Refresh Token Rotation). 탈취 감지 가능.

실전 — Google OAuth 시작하기

  1. Google Cloud Console 에서 OAuth client 만들기 → client_id + secret 발급. redirect_uri 등록.
  2. 사용자 클릭 시 https://accounts.google.com/o/oauth2/v2/auth으로 redirect (위 query 포함).
  3. callback 에서 code 받고 https://oauth2.googleapis.com/token POST 로 교환.
  4. access_token 으로 https://www.googleapis.com/oauth2/v3/userinfo 호출.

라이브러리: Auth.js (NextAuth), Passport, Authlib (Python), Spring Security 등이 위 단계를 모두 추상화.

요약

  • OAuth 2.0 = 위임. 인증이 필요하면 OIDC 추가.
  • 서버 앱 = Authorization Code, SPA/모바일 = + PKCE, 서버끼리 = Client Credentials.
  • Implicit / Password Grant 는 deprecated. 새 시스템에서 금지.
  • access token 짧게, refresh 로 갱신. token 은 HttpOnly 쿠키 권장.
  • scope 최소화. state + PKCE + redirect_uri 정확 매칭이 기본 방어.
가이드 목록으로