학습 목표
OAuth 2.0이 위임(delegation) 프레임워크임을 설명할 수 있다 (인증 프로토콜이 아님).
Resource Owner·Client·Authorization Server·Resource Server 역할을 구분할 수 있다.
grant type별 용도와 권장·비권장을 설명할 수 있다.
Authorization Code + PKCE 흐름을 단계별로 설명할 수 있다.
문제 상황
- 사진 앱이 구글 비밀번호를 직접 받는다
- 앱이 털리면 본계정까지 노출
- SPA가
client_secret을 프론트 번들에 넣었다- 공개 클라이언트에는 secret이 없다고 봐야 한다
- 모바일 앱이 implicit flow로 URL fragment에 토큰을 받았다
- 로그·리퍼러·브라우저 히스토리에 유출
- access token 하나로 모든 API에 무제한 접근
- scope·만료·철회 설계 없음
- "OAuth 로그인"이라고 하는데 누가 누구인지 검증이 없다
- 인증은 OpenID Connect(7편), OAuth는 접근 위임
JWT에서 토큰 형식·검증을 봤다. 이번 편은 제3자 앱에 내 리소스 접근을 허용하는 표준 흐름이다.
본 글의 흐름·예제는 방어·구현 학습 목적이다. 타인 서비스에 대한 무단 접근·토큰 탈취 시도는 불법이다.
1. OAuth 2.0이 하는 일
OAuth 2.0 (RFC 6749) — Resource Owner가 Client에게 제한된 접근을 위임하는 프레임워크.
- Client가 사용자 비밀번호를 저장하지 않음
- Authorization Server가 동의·토큰 발급
- Resource Server(API)는 access token으로 scope 내 요청만 허용
| OAuth 2.0 | 아닌 것 |
|---|---|
| "이 앱이 내 드라이브 읽기 해도 됨" | "로그인한 사용자 이메일이 누구" (OIDC) |
| access token 위임 | 세션 쿠키 대체만이 전부는 아님 |
2. 역할

| 역할 | 예 |
|---|---|
| Resource Owner | 사용자 (구글 계정 소유자) |
| Client | 사진 백업 앱, SPA, 모바일 앱 |
| Authorization Server | accounts.google.com, Keycloak, Auth0 |
| Resource Server | Google Drive API, 자사 API |
- Client는 client_id로 식별, 기밀 클라이언트만 client_secret 보유
- 토큰은 보통 Bearer —
Authorization: Bearer <access_token>
3. Grant type

| Grant | 용도 | 권장 |
|---|---|---|
| Authorization Code (+ PKCE) | 웹·SPA·모바일·네이티브 | 기본 선택 |
| Client Credentials | 서버↔서버, 배치, M2M | 기밀 클라이언트만 |
| Device Code | TV·CLI 등 입력 제한 기기 | 해당 UX에만 |
| Implicit | (과거 SPA) | 사용 금지 (RFC 폐기 방향) |
| Resource Owner Password | 레거시 | 신규 금지 |
- 공개 클라이언트(SPA, 모바일) — secret 없음 → PKCE 필수
- 기밀 클라이언트(백엔드) — secret + redirect URI 고정
4. Authorization Code + PKCE
PKCE (RFC 7636) — authorization code 가로채기(code interception) 방어.

사전: code_verifier / code_challenge
code_verifier = random 43~128 chars
code_challenge = BASE64URL(SHA256(code_verifier)) # method S256
흐름
- Client가
code_verifier·code_challenge생성 - 브라우저를 Authorization Server
/authorize로 리다이렉트
GET /authorize?
response_type=code
&client_id=photo-app
&redirect_uri=https://app.example/callback
&scope=files.read
&state=random-csrf-token
&code_challenge=E9Mel...
&code_challenge_method=S256
- 사용자 로그인·동의
redirect_uri?code=AUTH_CODE&state=...로 돌아옴 — state 검증 필수- Client(또는 BFF)가 백채널로
/token호출
POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://app.example/callback
&client_id=photo-app
&code_verifier=ORIGINAL_VERIFIER
- access_token (+ refresh_token) 수신
- Resource Server API 호출
GET /v1/files HTTP/1.1
Authorization: Bearer eyJhbGciOi...
- 토큰 교환은 반드시 서버 간(BFF·백엔드) — 브라우저에
client_secret·장기 refresh 노출 금지 - 예시 요청 전문은
code/oauth-requests.http참고
왜 PKCE인가
| 위협 | PKCE 없이 | PKCE |
|---|---|---|
| 공개 클라이언트에 code 탈취 | 토큰 발급 가능 | verifier 없으면 거부 |
| malicious app이 동일 redirect | code 재사용 시도 | challenge 검증 |
5. Client Credentials (M2M)
서비스 계정·배치 작업 — 사용자 동의 UI 없음.
POST /token
grant_type=client_credentials
&client_id=worker
&client_secret=...
&scope=internal.jobs
- secret은 Vault·환경변수 (13편) — 코드·이미지에 박지 않음
- scope를 최소로 (least privilege)
6. 보안 체크리스트
| 항목 | 내용 |
|---|---|
| redirect_uri | 등록된 값과 exact match — 와일드카드 최소화 |
| state | CSRF 방지 — 요청마다 랜덤, 콜백에서 검증 |
| PKCE | 공개 클라이언트 S256 필수 |
| scope | 필요한 권한만 — files.read vs files.write |
| token 저장 | SPA는 메모리·BFF — JWT 5편 저장 트레이드오프 |
| HTTPS | authorize·token·API 전 구간 |
| token 검증 | RS256 JWKS, exp·aud·iss (JWT 사용 시) |
| 로그아웃 | refresh 폐기·provider revoke endpoint |
| 안티패턴 | 이유 |
|---|---|
| implicit flow | 토큰이 URL fragment에 노출 |
| password grant | 피싱·자격 증명 유출 |
| long-lived access token | 탈취 시 피해 확대 — refresh rotation |
7. 정리
- OAuth 2.0 = 접근 위임 — 비밀번호 대신 scope 제한 토큰
- 웹·모바일·SPA는 Authorization Code + PKCE가 표준
- 공개 vs 기밀 클라이언트에 따라 secret·교환 위치가 다름
- "소셜 로그인"의 신원 확인은 OpenID Connect(7편)
다음에 다룰 것
- OpenID Connect
- ID token, identity layer
해당 내용은 RFC 6749 (OAuth 2.0), RFC 7636 (PKCE), OWASP OAuth2 Cheat Sheet 의 내용을 기반으로 합니다.