cd ../blog
DEEP DIVE

OAuth 2.0 Authorization Code Flow 동작 원리: 비밀번호 없는 인증이 필요한 이유

읽는 시간 약 13분
조회수 5
OAuth인증보안RFCAPI

OAuth 2.0 Authorization Code Flow의 동작 원리를 단계별로 분석합니다. Authorization Code와 Access Token의 이중 구조가 왜 필요한지, PKCE는 어떤 문제를 해결하는지, 그리고 실제 CVE 사례를 통해 구현 시 주의점을 설명합니다.

share:
OAuth 2.0 Authorization Code Flow 동작 원리: 비밀번호 없는 인증이 필요한 이유

Phase 1: 왜 만들어졌는가 — 비밀번호 공유의 위험

2010년 이전 웹 애플리케이션 시대를 상상해보세요. 트위터에 자동으로 글을 올려주는 서드파티 앱을 사용하려면, 그 앱에 트위터 아이디와 비밀번호를 직접 입력해야 했습니다. 이는 심각한 보안 문제를 야기했습니다.

기존 방식의 치명적 결함

비밀번호 직접 공유 방식은 다음과 같은 문제를 가졌습니다:

  • 전체 권한 노출: 제3자 앱이 사용자 계정의 모든 권한을 얻게 됨
  • 비밀번호 저장 위험: 서드파티 앱이 사용자 비밀번호를 저장해야 함
  • 선택적 권한 불가: 특정 기능만 허용할 수 없음
  • 권한 취소 불가: 비밀번호를 바꾸지 않으면 앱의 접근을 막을 수 없음

사용자가 여러 서드파티 앱을 사용한다면, 그만큼 많은 곳에 비밀번호를 맡겨야 했고, 그 중 한 곳이라도 해킹당하면 모든 계정이 위험해졌습니다.

한 줄 요약: OAuth 2.0 Authorization Code Flow는 비밀번호 공유 없이 제한된 권한을 안전하게 위임하기 위해 만들어졌습니다.

Phase 2: 핵심 개념 정의

한 문장 정의

OAuth 2.0 Authorization Code Flow는 사용자가 비밀번호를 공유하지 않고도 제3자 애플리케이션이 특정 권한으로 사용자 데이터에 접근할 수 있게 하는 인증 방식입니다.

공식 정의

The authorization code grant type is used to obtain both access tokens and refresh tokens and is optimized for confidential clients. Since this is a redirection-based flow, the client must be capable of interacting with the resource owner's user-agent (typically a web browser) and capable of receiving incoming requests (via redirection) from the authorization server. — RFC 6749, Section 4.1

RFC 정의를 풀어보면, Authorization Code Grant는 액세스 토큰과 리프레시 토큰을 모두 얻을 수 있으며, 클라이언트 비밀을 안전하게 보관할 수 있는 기밀 클라이언트에 최적화된 방식입니다.

핵심 구성 요소

  1. Resource Owner (리소스 소유자): 보호된 리소스에 대한 액세스를 부여할 수 있는 엔티티 (보통 사용자)
  2. Client (클라이언트): 리소스 소유자를 대신하여 보호된 리소스 요청을 하는 애플리케이션
  3. Authorization Server (권한 부여 서버): 리소스 소유자를 성공적으로 인증하고 권한을 얻은 후 클라이언트에게 액세스 토큰을 발행하는 서버
  4. Resource Server (리소스 서버): 보호된 리소스를 호스팅하고 액세스 토큰을 사용하여 보호된 리소스 요청을 수락하고 응답할 수 있는 서버

관련 용어 비교표

용어설명
Authorization Code액세스 토큰 교환용 임시 코드. 일회성이며 수 분 내 만료
Access TokenAPI 호출 시 사용하는 토큰. 제한된 권한(scope), 1-24시간 유효
Refresh Token액세스 토큰 갱신용 장기 토큰. 30일-1년 유효
Client Secret클라이언트 앱 인증용 비밀키. 서버에만 저장, 수동 갱신

Phase 3: 동작 원리 — 내부에서 일어나는 일

전체 흐름 개요

OAuth 2.0 Authorization Code Flow는 이중 인증 구조 를 가집니다. 먼저 사용자가 권한을 부여하면 임시 코드를 받고, 이 코드를 실제 토큰으로 교환하는 2단계 과정을 거칩니다.

이 흐름에서 핵심은 5단계와 6단계의 분리 입니다. Authorization Code(5단계)만으로는 리소스에 접근할 수 없고, 반드시 Client Secret과 함께 토큰 교환(6단계)을 거쳐야 합니다. 이 이중 구조가 OAuth 2.0 Authorization Code Flow의 보안 핵심입니다.

1단계: 권한 부여 요청 (Authorization Request)

사용자가 제3자 앱에서 "Google로 로그인" 버튼을 클릭하면, 앱은 사용자를 Google의 권한 부여 서버로 리다이렉트합니다.

GET /oauth/authorize? response_type=code& client_id=s6BhdRkqt3& state=xyz& redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb& scope=read_profile Host: authorization-server.com

각 파라미터의 역할:

  • response_type=code: Authorization Code를 요청한다는 의미
  • client_id: 미리 등록된 클라이언트 앱의 식별자
  • state: CSRF 공격 방지용 랜덤 값
  • redirect_uri: 인증 완료 후 돌아올 주소
  • scope: 요청하는 권한 범위

2단계: 사용자 인증 및 권한 승인

권한 부여 서버는 사용자에게 로그인 화면과 권한 승인 화면을 보여줍니다. 사용자가 승인하면 Authorization Code가 생성됩니다.

3단계: Authorization Code 반환

HTTP/1.1 302 Found Location: https://client.example.com/cb? code=SplxlOBeZQQYbYS6WxSbIA& state=xyz

핵심 보안 특징:

  • code일회성 이며 짧은 시간 내에 사용해야 함 (RFC 6749 Section 4.1.2에서는 최대 10분을 권고)
  • state 값이 1단계와 동일한지 검증하여 CSRF 공격 방지
  • Authorization Code 자체로는 아무것도 할 수 없음 (추가 인증 필요)

4단계: 액세스 토큰 교환 (Token Exchange)

클라이언트 앱은 받은 Authorization Code와 자신의 Client Secret 을 함께 권한 부여 서버에 전송합니다.

POST /oauth/token HTTP/1.1 Host: authorization-server.com Content-Type: application/x-www-form-urlencoded grant_type=authorization_code& code=SplxlOBeZQQYbYS6WxSbIA& redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb& client_id=s6BhdRkqt3& client_secret=gX1fBat3bV

왜 Client Secret이 필요한가:

  • Authorization Code만으로는 부족함
  • 클라이언트 앱의 정당성을 추가로 검증
  • 중간자 공격(MITM)으로 코드를 탈취해도 토큰 교환 불가

5단계: 액세스 토큰 발급

{ "access_token": "2YotnFZFEjr1zCsicMWpAA", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA", "scope": "read_profile" }

토큰 구조 분석:

  • access_token: 실제 API 호출에 사용하는 토큰
  • expires_in: 3600초(1시간) 후 만료
  • refresh_token: 액세스 토큰 갱신용 장기 토큰
  • scope: 실제로 부여된 권한 범위

6단계: API 호출

GET /api/profile HTTP/1.1 Host: resource-server.com Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA

리소스 서버는 액세스 토큰을 검증하고 요청된 리소스를 반환합니다.

일상 비유로 이해하기

OAuth 2.0 Authorization Code Flow는 호텔 키카드 발급 과정 과 비슷합니다:

  1. 프론트 데스크 방문: 사용자가 권한 부여 서버에 접근
  2. 신분증 확인: 사용자 인증 (로그인)
  3. 임시 영수증 발급: Authorization Code 발급
  4. 키카드 교환: 영수증 + 예약 확인서(Client Secret)로 실제 키카드(Access Token) 받기
  5. 객실 입장: 키카드로 보호된 리소스 접근

임시 영수증(Authorization Code)만으로는 객실에 들어갈 수 없고, 반드시 예약 확인서와 함께 제시해야 키카드를 받을 수 있는 구조입니다.

토큰 검증 흐름

리소스 서버가 액세스 토큰을 받으면 어떻게 검증할까요?

토큰 검증은 다음 순서로 진행됩니다:

  1. 헤더 확인: Authorization: Bearer <token> 형식인지 검사
  2. 토큰 파싱: JWT인 경우 서명 검증, 불투명 토큰인 경우 인트로스펙션 엔드포인트 호출
  3. 만료 시간 확인: exp 클레임이 현재 시간 이후인지 검사
  4. Scope 검증: 요청된 리소스에 접근할 권한이 있는지 확인

토큰 갱신 과정 (Refresh Token 사용)

액세스 토큰이 만료되면 Refresh Token으로 새로운 액세스 토큰을 발급받습니다:

POST /oauth/token HTTP/1.1 Host: authorization-server.com Content-Type: application/x-www-form-urlencoded grant_type=refresh_token& refresh_token=tGzv3JOkF0XG5Qx2TlKWIA& client_id=s6BhdRkqt3& client_secret=gX1fBat3bV

Refresh Token의 보안 설계:

  • 사용자 재인증 없이 토큰 갱신 가능
  • 액세스 토큰보다 훨씬 긴 수명 (보통 30일-1년)
  • 탈취 시 피해를 줄이기 위해 토큰 로테이션 적용 (새 Refresh Token 발급)

엣지 케이스와 변형

PKCE (Proof Key for Code Exchange) 확장

모바일 앱이나 SPA(Single Page Application)처럼 Client Secret을 안전하게 저장할 수 없는 환경에서는 PKCE 를 사용합니다:

# 1단계: code_challenge 생성 GET /oauth/authorize? response_type=code& client_id=s6BhdRkqt3& code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM& code_challenge_method=S256& redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb # 2단계: code_verifier로 검증 POST /oauth/token grant_type=authorization_code& code=SplxlOBeZQQYbYS6WxSbIA& code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

PKCE는 Client Secret 대신 동적으로 생성된 code_challenge/code_verifier 쌍 을 사용하여 보안을 강화합니다.

왜 Authorization Code를 바로 사용하지 않는가?

Authorization Code Flow의 핵심 질문: "왜 Authorization Server가 바로 Access Token을 주지 않고 굳이 Code를 거치는가?"

이유 1: 프론트채널 vs 백채널 분리

  • Authorization Code는 브라우저 리다이렉트(프론트채널)로 전달됨 → 노출 위험
  • Access Token 교환은 서버 간 직접 통신(백채널)으로 수행 → 안전

이유 2: Client 인증

  • Authorization Code만으로는 어떤 앱이 요청했는지 확인 불가
  • Token 교환 시 Client Secret을 함께 제출하여 앱의 정당성 검증

이유 3: 일회성 보장

  • Authorization Code는 한 번 사용되면 즉시 무효화
  • 코드 탈취 시에도 이미 사용된 후라면 공격 실패
브라우저 (프론트채널) 서버 (백채널) │ │ │ ←── Authorization Code ───│ (노출 가능하지만 단독 사용 불가) │ │ │ │──→ Code + Client Secret ──→ Auth Server │ │←── Access Token ←─────────────│ │ │ (안전한 서버 간 통신)

Phase 4: 보안 관점 — 공격자는 이걸 어떻게 보는가

공격자가 노리는 약점

OAuth 2.0 Authorization Code Flow에서 공격자는 주로 Authorization Code 탈취토큰 교환 과정 조작 을 노립니다. 이 흐름의 핵심 약점은 브라우저 리다이렉트 에 의존한다는 점입니다.

1) Redirect URI 검증 우회 공격

CVE-2023-6927: Keycloak 오픈 리다이렉트 취약점

공격 시나리오: Keycloak < 23.0.4 버전에서 와일드카드로 끝나는 redirect URI를 가진 OAuth 2.0 클라이언트는 redirect URI 검증을 우회할 수 있었습니다.

# 정상적인 redirect URI https://app.example.com/callback # 공격자가 조작한 URI https://app.example.com/callback/../../../evil.com/steal

공격 과정:

  1. 공격자가 조작된 redirect URI로 OAuth 요청 생성
  2. 피해자가 정상적으로 로그인하고 권한 승인
  3. Authorization Code가 공격자 제어 도메인으로 전송
  4. 공격자가 탈취한 코드로 액세스 토큰 교환

방어 방법:

  • Redirect URI 정확한 매칭 검증 (와일드카드 금지)
  • 허용된 URI 목록을 화이트리스트 방식으로 관리
  • URI 정규화 후 검증 수행

2) Authorization Code 중간 탈취

CVE-2023-28131: Expo Auth Session 라이브러리 취약점

공격 메커니즘: expo-auth-session 라이브러리에서 authorization code가 안전하지 않은 채널을 통해 전송되어 공격자가 탈취할 수 있었습니다.

// 취약한 코드 예시 const result = await AuthSession.startAsync({ authUrl: `https://provider.com/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}`, // returnUrl이 안전하지 않게 처리됨 }); // 공격자가 authorization code 탈취 후 토큰 교환 const tokenResponse = await fetch('https://provider.com/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `grant_type=authorization_code&code=${stolenCode}&client_id=${clientId}` });

피해 범위:

  • Facebook, Google, Twitter 등 주요 플랫폼 계정 탈취
  • 신용카드 정보 접근
  • 완전한 계정 장악

3) ConsentFix 공격 (2025년 발견)

공격 원리: ConsentFix는 OAuth 2.0 authorization code flow를 악용하여 Microsoft Entra ID의 장치 규정 준수 확인과 조건부 액세스 정책을 우회합니다.

# 공격자가 생성한 악의적 로그인 URI https://login.microsoftonline.com/common/oauth2/v2.0/authorize? client_id=legitimate_client_id& response_type=code& redirect_uri=http://localhost:8080/callback& scope=https://graph.microsoft.com/.default& state=malicious_state

공격 단계:

  1. 공격자가 합법적인 Microsoft Entra 로그인 URI 생성
  2. localhost redirect를 이용하여 피해자로부터 authorization code 추출
  3. 탈취한 코드로 권한 있는 access token 획득
  4. 장치 정책과 조건부 액세스 우회

4) State 파라미터 누락 (CSRF 공격)

공격 코드:

<!-- 공격자가 피해자에게 전송하는 악의적 링크 --> <a href="https://oauth-provider.com/authorize? response_type=code& client_id=victim_app_id& redirect_uri=https://victim-app.com/callback& scope=read_write <!-- state 파라미터 의도적 누락 --> ">무료 쿠폰 받기</a>

피해자가 이미 OAuth 제공자에 로그인된 상태라면, 공격자의 계정이 피해자의 OAuth 앱 계정과 연결됩니다.

올바른 구현 체크리스트

Authorization Server 측

  • Redirect URI 정확한 매칭: 와일드카드나 부분 매칭 금지
  • Authorization Code 수명: RFC 권고에 따라 최대 10분 이내로 제한
  • Code 일회성 보장: 사용된 코드는 즉시 무효화
  • State 파라미터 검증: 요청-응답 간 state 값 일치 확인

Client 측

  • Client Secret 보호: 서버 사이드에만 저장, 브라우저/앱에 노출 금지
  • HTTPS 강제: 모든 OAuth 통신에 TLS 적용
  • PKCE 적용: 공개 클라이언트(모바일/SPA)에서 반드시 사용
  • 토큰 안전 저장: 액세스 토큰을 안전한 저장소에 보관

Phase 5: 정리와 연결

OAuth 2.0 Authorization Code Flow는 비밀번호 공유 없이 안전한 권한 위임을 구현하는 표준 방식으로, 이중 인증 구조를 통해 보안을 강화했지만 구현 과정에서 다양한 취약점이 발생할 수 있어 신중한 설계가 필요합니다.

현대 웹 보안에서 OAuth는 핵심 인증 인프라가 되었지만, 네트워크 ACL 우회: 방화벽 정책을 무력화하는 원리에서 다룬 것처럼 보안 정책 우회 공격의 대상이 되기도 합니다. 특히 다운그레이드 공격: 보안 기능이 무력화되는 원리에서 설명한 보안 기능 무력화 패턴은 OAuth 구현에서도 자주 발견됩니다.

관련 글

OAuth 2.0에서 사용하는 토큰의 구조를 이해하려면 JWT를 알아야 합니다. JWT 동작 원리와 보안 취약점에서 토큰의 서명 검증 원리와 보안 취약점을 확인해보세요.

다음에 알아볼 심화 주제:

  • OpenID Connect (OIDC): OAuth 2.0 위에 구축된 신원 인증 레이어
  • OAuth 2.1: 현재 개발 중인 차세대 OAuth 스펙

참고 자료


안내 및 법적 고지

AI 활용 안내 이 글은 AI(Claude)의 도움을 받아 작성되었습니다. 기술적 내용은 공식 문서, RFC, 신뢰할 수 있는 자료를 기반으로 하며, 보안 교육 목적으로 제공됩니다.

면책 조항 본 글은 보안 인식 제고를 위한 교육 목적으로 작성되었습니다. 언급된 공격 기법을 실제로 시도하는 행위는 「정보통신망법」, 「형법」 등에 따라 처벌받을 수 있으며, 본 블로그는 이에 대한 법적 책임을 지지 않습니다.

COMMENTS (0)

댓글은 익명으로 작성되며, 삭제 비밀번호를 설정하면 본인만 삭제할 수 있습니다. 비밀번호를 설정하지 않은 댓글은 누구나 삭제할 수 있습니다.