OAuth2 and OIDC
PKCE(Proof Key for Code Exchange): 코드 탈취 방지. SPA/모바일 앱에서 필수.
Article
M
Me
hyunyoun's Blog
software-engineering-devopssoftware-engineeringdev-opsarchitecturedesignoauth2oidcoauth2-and-oidc11 min read
1. Overview
OAuth2와 OIDC(OpenID Connect)는 내 서비스의 아이디와 비밀번호를 다른 서비스에 넘겨주는 미친 짓을 멈추고, 호텔 키 카드처럼 '제한된 출입증(Token)'만을 발급해 주는 현대 웹의 탈중앙화된 신원 위임(Identity Delegation) 역학을 해부합니다.
학습자는 제3자 앱(Client)이 내 구글 캘린더에 접근할 때 구글 비밀번호를 직접 요구하던 과거의 보안 파국을 뜯어봅니다. 나아가 권한 부여(Authorization)를 위한 **OAuth2의 4가지 역할(Resource Owner, Client, Auth Server, Resource Server)**과 토큰(Access Token)의 교환 메커니즘을 장악합니다. 마지막으로, OAuth2는 '출입증'일 뿐 '신분증'이 아님을 인지하고, 신원 인증(Authentication)을 완벽히 수행하기 위해 JWT 기반의 신분증(ID Token)을 덧붙인 OIDC(OpenID Connect) 역량을 확보합니다.
2. Scope & Boundaries
In-Scope
- OAuth2 Physics: 인가(Authorization), Access Token, Refresh Token, 4 Roles (Resource Owner, Client, Authorization Server, Resource Server).
- OAuth2 Flows: Authorization Code Grant (가장 안전한 표준), Implicit Grant (사양됨), Client Credentials Grant (기계 간 통신).
- OIDC (OpenID Connect): 인증(Authentication), ID Token, JWT(JSON Web Token).
- Security Boundaries: CSRF 방어를 위한
state파라미터, 인가 코드(Code) 탈취 방지를 위한 PKCE.
Out-of-Scope
- JWT 암호학적 서명(Signature): RSA, HMAC 등의 수학적 암호화/복호화 알고리즘 10. Security & Cryptography로 위임.
- Spring Security / Passport.js 구현체: 특정 언어 프레임워크의 OAuth2 클라이언트 세팅 코드 프레임워크 매뉴얼로 분리.
Boundaries
- Authentication(인증) vs Authorization(인가): OAuth2의 가장 거대한 오해를 부수어 버립니다. OAuth2는 "이 문을 통과할 권한(인가)"을 주는 호텔 키 카드일 뿐, "너의 이름이 무엇인지(인증)"를 증명하는 신분증이 아닙니다. OAuth2로 로그인을 구현하는 것은 편법이었으며, 이 한계를 극복하기 위해 OAuth2 위에 신분증(ID Token) 계층을 얹은 것이 OIDC임을 명확히 긋습니다.
3. Counterexample
- 비밀번호의 양도 (The Anti-Pattern): 옛날엔 '페이스북 친구 찾기' 앱이 페이스북 아이디와 비밀번호를 직접 입력하라고 요구했습니다. 앱은 내 비밀번호를 DB에 저장해 두고 밤마다 내 계정으로 로그인해 데이터를 빼갔습니다. 만약 이 앱이 해킹당하면 내 페이스북은 통째로 털립니다. '제한된 토큰'이 아니라 '마스터키(비밀번호)'를 넘겨버린 통제권 상실의 비극입니다.
- 토큰 탈취 (Implicit Grant): 주니어가 모바일 앱에 OAuth2를 달면서, 브라우저 주소창(URL)으로 Access Token을 바로 쏴주는 'Implicit Grant' 방식을 썼습니다. 지하철 공용 와이파이 해커가 패킷을 스니핑해서 주소창의 토큰을 훔쳐 내 계정을 탈취했습니다. 토큰을 주소창에 노출하는 방식의 치명적 설계 결함을 뜯어봅니다.
4. Prerequisites
- HTTP와 통신 (Basic): HTTP Request, Redirect(302), HTTPS (SSL/TLS). (08. Network)
- 인가와 인증 (Basic): Session/Cookie 기반의 전통적인 로그인 방식. (08. Network)
5. Learning Map
6. Learning Topics
Basic
Core Topic 01: 호텔 키 카드의 발급 (The Anatomy of OAuth2)
- Why to Learn: 사용자의 구글 비밀번호를 내 서비스 DB에 저장하는 멍청한 짓을 막고, 언제든 구글 쪽에서 권한을 취소할 수 있는 '안전한 대리인(Delegation)' 아키텍처를 설계하기 위함입니다.
- What to Learn:
- Concepts: OAuth 2.0, Resource Owner (사용자), Client (내 서비스), Authorization Server (구글 인증 서버), Resource Server (구글 캘린더 API).
- Skills: "내 서비스가 구글 캘린더에 접근한다"는 시나리오에서 4가지 역할(Role)을 정확히 매핑하고, Access Token의 라이프사이클을 설계하기.
- How to Learn:
- 1단계: 등장인물 4인방: 당신(Resource Owner)은 내 서비스(Client)에게 구글 캘린더(Resource Server)를 볼 권한을 주려 합니다. 허락을 내리는 곳은 구글 인증 서버(Authorization Server)입니다.
- 2단계: 호텔의 비유: 당신은 호텔 방(자원)의 주인입니다. 친구(Client)에게 방에 있는 짐을 가져오라고 시킵니다. 마스터키(비밀번호)를 주면 친구가 금고까지 다 열어봅니다. 그래서 프론트데스크(Auth Server)에 가서 "내 친구에게 1시간 동안 문만 열 수 있는 '키 카드(Token)'를 줘"라고 허락(Grant)합니다.
- 3단계: 권한의 범위 (Scope): 키 카드를 발급할 때, 구글은 묻습니다. "이 앱이 당신의 캘린더를 '읽기'만 할 건가요, '쓰기'도 할 건가요?" 이것이 Scope입니다. 최소 권한의 원칙에 따라 통제권을 완벽히 분리하는 위임의 물리법칙을 해부합니다.
- Implement: OAuth2 등장인물 매핑 렌더링.
요구사항: '스파르타코딩' 앱이 '카카오톡' 친구 목록을 불러온다.Resource Owner: 스마트폰을 들고 있는 '나'.Client: '스파르타코딩' 앱.Auth Server: 카카오 로그인 서버 (토큰 발급).Resource Server: 카카오 친구 API 서버 (토큰 검증 후 데이터 반환). 권력의 4분할 시각화.
Recommended
Core Topic 02: 인가 코드의 교환술 (Authorization Code Grant)
- Why to Learn: 구글이 토큰을 발급할 때 브라우저 주소창(URL)으로 던져주면 지나가는 해커가 다 훔쳐보므로, 오직 백엔드 서버끼리만 은밀하게 토큰을 교환하는 가장 안전한 표준 흐름을 짜기 위함입니다.
- What to Learn:
- Concepts: Authorization Code (인가 코드), Redirect URI, Client ID & Client Secret, Back-channel (백채널 통신).
- Skills: 프론트엔드가 주소창으로 '토큰' 대신 일회용 '인가 코드(Code)'를 받아 백엔드에 넘기면, 백엔드가
Client Secret을 들고 구글과 몰래 통신(Back-channel)하여 진짜 '토큰'으로 교환하는 3-Way 댄스 설계하기.
- How to Learn:
- 1단계: 주소창의 공포 (Implicit): 구글 로그인을 성공했습니다. 구글이 주소창에
myapp.com/callback?token=abcdef라고 쏴줍니다. 이 URL은 브라우저 히스토리, 통신사 프록시, 카페 와이파이에 전부 기록(노출)됩니다. 최악입니다. - 2단계: 교환권의 발행 (Authorization Code): 구글은 토큰 대신 1분짜리 임시 '교환권(Code)'을 주소창으로 쏴줍니다.
myapp.com/callback?code=12345. 해커가 이걸 훔쳐도, 이 교환권은 껍데기일 뿐입니다. - 3단계: 백채널 통신 (Back-channel): 내 백엔드 서버가 프론트에서 넘어온 이
code를 받습니다. 그리고 아무도 훔쳐볼 수 없는 서버-투-서버(Server-to-server) 통신으로 구글에 접속해code와Client Secret(서버만 아는 비밀번호)을 내밀고, 비로소 진짜Access Token을 받아옵니다. 프론트엔드(브라우저)에는 절대 토큰이 노출되지 않는 극강의 보안 댄스를 뜯어봅니다.
- 1단계: 주소창의 공포 (Implicit): 구글 로그인을 성공했습니다. 구글이 주소창에
- Implement: Authorization Code Grant 시퀀스 다이어그램 모사.
Step 1: 유저가 구글 로그인. 구글 프론트엔드 (302 Redirect withcode).Step 2: 프론트엔드 내 백엔드 (code전달).Step 3: 내 백엔드 구글 Auth 서버 (code+client_secret전달).Step 4: 구글 Auth 서버 내 백엔드 (Access Token발급). 해커가code를 탈취해도client_secret이 없어 토큰 교환에 실패하는 완벽한 방어 시각화.
Practical
Core Topic 03: 출입증과 신분증의 융합 (OIDC - OpenID Connect)
- Why to Learn: "OAuth2로 로그인을 구현했습니다!"라는 주니어에게 "OAuth2는 인가(Authorization) 프레임워크지, 인증(Authentication) 프로토콜이 아니다"라는 충격적인 진실을 알려주고 OIDC라는 신분증을 발급받기 위함입니다.
- What to Learn:
- Concepts: OpenID Connect (OIDC), ID Token, Authentication vs Authorization, JWT (JSON Web Token), UserInfo Endpoint.
- Skills: OAuth2로 받아온 Access Token을 들고 다시 구글 API를 찔러 유저 정보를 가져오는 헛수고를 버리고, 한 번에 JWT 형식의
id_token을 받아와 디코딩(Decoding)하여 즉시 로그인 처리하기.
- How to Learn:
- 1단계: 가짜 로그인 (Pseudo-Authentication): OAuth2 토큰은 "캘린더를 볼 권한"입니다. 옛날엔 이 토큰을 받으면 "아, 구글이 토큰을 줬으니 정상 유저구나" 하고 그냥 로그인 처리를 했습니다. 만약 해커가 다른 앱에서 탈취한 토큰을 내 앱에 들이밀면? 내 앱은 누군지도 모르고 문을 열어줍니다(Confused Deputy Problem).
- 2단계: 신분증의 탄생 (OIDC): 권한(출입증) 말고 신원(신분증)을 증명할 표준이 필요했습니다. 구글, 마이크로소프트 등이 모여 OAuth2 위에 신원 인증 레이어인 OIDC를 올렸습니다.
- 3단계: ID Token (JWT): OIDC를 쓰면(scope에
openid추가), 구글은 Access Token과 함께id_token을 던져줍니다. 이 안에는 "이름: 홍길동, 이메일: hong@, 발급자: 구글, 발급대상: 내 앱"이라는 정보가 위변조 불가능한 서명(JWT)과 함께 들어있습니다. 서버를 찌를 필요 없이 즉시 유저를 인증하는 프로토콜의 진화를 해부합니다.
- Implement: ID Token 디코딩 및 검증(Validation) 렌더링.
Action: 구글에서 받은id_token의 Payload를 Base64 디코딩.Payload:{ "iss": "https://accounts.google.com", "aud": "내앱의_클라이언트ID", "sub": "1234567890", "email": "test@gmail.com" }.검증: "발급자(iss)가 구글인가? 수신자(aud)가 우리 앱이 맞는가?"를 체크하여 탈취 토큰의 릴레이 공격을 완벽히 차단하는 시각화.
Advanced
Core Topic 04: 모바일 앱의 딜레마와 PKCE (Proof Key for Code Exchange)
- Why to Learn: 모바일 앱(iOS/Android) 환경에서는
Client Secret을 소스코드에 안전하게 숨길 방법이 전혀 없으므로, Secret 없이도 인가 코드가 탈취당하는 것을 막는 암호학적 자물쇠(PKCE)를 채우기 위함입니다. - What to Learn:
- Concepts: Public Client vs Confidential Client, PKCE (Proof Key for Code Exchange), Code Verifier, Code Challenge, SHA-256.
- Skills: 프론트엔드(SPA)나 모바일 앱처럼 코드가 100% 털리는 환경(Public Client)에서 OAuth2를 구현할 때, 동적으로 생성한 난수(Verifier)와 해시값(Challenge)을 이용해 탈취범을 엿먹이기.
- How to Learn:
- 1단계: 비밀의 부재 (Public Client): 백엔드 서버(Confidential)는
client_secret을.env에 숨길 수 있습니다. 하지만 모바일 앱(Public)에 하드코딩하면 앱을 디컴파일해서 다 털립니다. Secret을 쓸 수 없습니다. - 2단계: 인터셉트 공격: Secret이 없으니 모바일 앱은 그냥
인가 코드(code)만 구글에 던지면 토큰을 받습니다. 악성 앱이 내 스마트폰에 깔려서,myapp://으로 돌아오는code를 가로채서 구글에 던집니다. 구글은 Secret을 검사 안 하니 그냥 토큰을 내줍니다. 계정이 털렸습니다. - 3단계: PKCE의 맹세: 로그인을 시작할 때, 앱이 난수(Verifier)를 하나 만들고 이를 SHA-256으로 해시(Challenge)해서 구글에 던집니다. "나중에 code 교환하러 올 놈은 이 해시의 원본(Verifier)을 알고 있는 놈이어야 해!" 나중에 악성 앱이 가로챈
code를 들고 구글에 가봤자 원본 난수를 모르므로 거절당합니다. 상태 유지를 기반으로 한 동적 방어술을 뜯어봅니다.
- 1단계: 비밀의 부재 (Public Client): 백엔드 서버(Confidential)는
- Implement: PKCE 암호학적 증명 프로세스 모사.
앱: 난수apple(Verifier) 생성 해시XYZ123(Challenge) 생성.Step 1: 앱 구글. 로그인 요청 시Challenge=XYZ123동봉.Step 2: 구글 앱.code=777발급. (악성 앱이777탈취).Step 3 (악성 앱): 구글에code=777제출. 구글: "Verifier 내놔." 악성 앱: "모름." 차단.Step 4 (정상 앱): 구글에code=777+Verifier=apple제출. 토큰 정상 발급. Secret 없이도 완벽한 인증을 구현하는 마법 렌더링.
7. Terminology
8. References
Primary
- [P1] CS2023 - Security (SEC) - Authentication and Authorization (OAuth2, OIDC)
- [P5] SFIA - Information Security (SCTY) - Access control protocols
Secondary
- [RFC 6749] The OAuth 2.0 Authorization Framework
- [OpenID Connect Core 1.0] - OIDC specification and ID Token (JWT)
Industry
- [Auth0 Docs] - Which OAuth 2.0 Flow Should I Use? (Authorization Code with PKCE)
- [Okta Developer] - OAuth 2.0 and OpenID Connect (in plain English)
9. Final Checklist
Primary
- 내 서비스(Client)가 구글 캘린더 API(Resource Server)에 접근하기 위해 사용자의 아이디/비밀번호를 통째로 넘겨받는 옛날 방식의 보안적 파국을 설명하고, OAuth2가 호텔 키 카드(Token) 발급을 통해 이를 어떻게 해결했는지 논증할 수 있는가?
- OAuth2 흐름 중 토큰을 주소창으로 직접 쏴주는 암묵적 승인(Implicit Grant) 방식이 폐기(Deprecated)된 이유를 설명하고, 인가 코드(Code)를 받은 뒤 백엔드 서버끼리 통신(Back-channel)하여 토큰을 교환하는 Authorization Code Grant의 3-way 구조를 설계할 수 있는가?
Secondary
- "OAuth 2.0은 인가(Authorization)를 위한 것이지 인증(Authentication)을 위한 것이 아니다"라는 명제를 기반으로, 악의적인 앱이 탈취한 토큰을 내 서비스에 들이밀어 로그인을 뚫어내는 취약점을 증명할 수 있는가?
- OAuth 2.0의 한계를 극복하기 위해 OIDC(OpenID Connect)가 발급하는
id_token(JWT)의 내부 구조(Payload의 iss, aud, sub)를 디코딩하여, 서버를 한 번 더 찌르지 않고도 즉시 사용자의 신원(신분증)을 안전하게 검증(Validation)할 수 있는가?
Industry
- 클라이언트 시크릿(Client Secret)을 안전하게 보관할 수 없는 모바일 앱이나 React 기반의 SPA(Public Client) 환경에서, 악성 앱이 딥링크(URI Scheme)를 가로채 인가 코드를 탈취하는 공격을 어떻게 방어할 것인가?
- 위 탈취 공격을 무력화하기 위해, 로그인 시작 시 동적으로 난수(Verifier)를 생성하고 해시(Challenge)하여 구글에 던져두는 PKCE(Proof Key for Code Exchange)의 암호학적 상태 유지 매커니즘을 렌더링할 수 있는가?