모바일 애플리케이션과 같은 공개 클라이언트 환경에서 클라이언트 시크릿 없이 안전하게 OAuth 2.0 인증을 구현하는 표준 방법은 PKCE(Proof Key for Code Exchange) 워크플로우를 적용하는 것입니다. PKCE는 클라이언트가 임시로 생성한 비밀값(Code Verifier)을 기반으로 해시값(Code Challenge)을 생성하고, 이 증명 과정을 통해 인증 코드를 교환하는 메커니즘입니다. 이 방법론은 서버에 비밀 정보를 저장할 수 없는 모바일/SPA 구조적 한계를 해결하며, 현재 업계에서 가장 신뢰받는 인증 표준입니다.
OAuth 2.0에서 PKCE가 필수적인 이유: 공개 클라이언트의 보안 취약점
전통적인 OAuth 2.0 흐름에서 클라이언트 시크릿(Client Secret)을 사용하는 방식은 백엔드 서버 애플리케이션에는 적합하지만, 모바일 앱이나 SPA와 같은 ‘공개 클라이언트(Public Client)’ 환경에서는 근본적인 보안 취약점을 가집니다.
모바일 앱은 사용자의 기기에서 실행되므로, 루팅(Rooting)이나 역공학(Reverse Engineering) 공격을 통해 저장된 시크릿 값이 추출될 위험이 항상 존재합니다. 따라서 공개 클라이언트가 시크릿을 안전하게 보관할 수 없기 때문에, 시크릿 대신 동적인 증명 방식을 사용하는 것이 필수적입니다.
PKCE가 없다면, 공격자는 오픈 와이파이 환경 등에서 인증 요청 과정 중 발생하는 인가 코드(Authorization Code)를 가로채는 ‘코드 가로채기(Code Interception)’ 공격에 매우 취약합니다. PKCE는 이 문제를 해결하기 위해, 단순한 코드 교환을 넘어 해당 요청이 진짜 앱에서 발생했음을 수학적으로 증명하는 ‘일회성 비밀 증명’을 도입했습니다.
PKCE 기반 인증 흐름 이해하기: 6단계 실전 과정
PKCE 워크플로우는 클라이언트 시크릿을 대체하여, 앱이 실행될 때마다 새롭게 생성되는 일회용 동적 비밀값에 의존합니다. 이 과정은 다음과 같은 6단계로 진행됩니다.
1단계: 동적 비밀값 생성 (Code Verifier)
앱이 시작되는 시점에 암호학적으로 안전한 난수 생성기를 사용하여 code_verifier를 생성합니다. 이 값은 최소 32바이트 이상의 엔트로피를 가져야 하며, 절대 하드코딩하거나 재사용해서는 안 됩니다.
2단계: 증명 키 생성 (Code Challenge)
생성된 code_verifier를 SHA256과 같은 표준 해시 함수를 이용해 해시화합니다. 이 해시 결과가 바로 code_challenge가 됩니다. 이 과정이 보안의 핵심 역할을 합니다.
3단계: 인가 요청 (Authorization Request)
사용자를 인증 서버의 인가 엔드포인트로 리디렉션 할 때, 생성된 code_challenge와 함께 이 값을 서버에 함께 전달합니다.
4단계: 인가 코드 획득 (Authorization Code Acquisition)
사용자가 로그인을 완료하면, 클라이언트는 인가 서버로부터 일회성 인가 코드를 획득합니다.
5단계: 토큰 교환 요청 (Token Exchange Request)
클라이언트는 백엔드 통신을 통해 토큰 엔드포인트로 요청을 보냅니다. 이때, 단순히 인가 코드만 보내는 것이 아니라, 1단계에서 생성했던 원래의 code_verifier를 반드시 함께 전송해야 합니다.
6단계: 토큰 발급 및 검증 (Token Issuance)
서버는 전송받은 code_verifier를 사용하여, 자신이 처음에 받은 code_challenge와 일치하는지 수학적으로 검증합니다. 이 검증이 성공했을 때만, 액세스 토큰을 발급합니다.
이 흐름에서 공격자가 중간에 인가 코드만 가로채더라도, 5단계에서 요구하는 code_verifier가 없기 때문에 토큰을 탈취할 수 없습니다.
PKCE 워크플로우 데이터 흐름 요약
PKCE의 핵심 데이터 교환 과정은 아래 표를 통해 명확히 이해할 수 있습니다.
| 단계 | 사용자 액션 | 전송 데이터 | 보안 기능 |
|---|---|---|---|
| 1. 초기화 | 클라이언트 생성 | `code_challenge` | 요청의 무결성 보장 |
| 2. 인증 요청 | 사용자 로그인 | `code` (인가 코드) | 임시 접근 권한 부여 |
| 3. 토큰 교환 | 클라이언트 요청 | `code` + `code_verifier` | **최종 인증 및 토큰 발급** |
핵심 보안 고려 사항
- State 관리: 공격자가 중간에 요청을 가로채더라도 세션 상태(State)를 확인하여 요청이 유효한지 검증해야 합니다.
- Verifier 보호:
code_verifier는 절대 외부에 노출되거나 저장되어서는 안 되며, 오직 토큰 교환 시에만 사용되어야 합니다. - HTTPS 필수: 모든 통신은 반드시 HTTPS를 통해 이루어져야 합니다.
결론적으로, PKCE(Proof Key for Code Exchange) 방식을 통해 클라이언트가 임의로 토큰을 획득하는 것을 원천적으로 차단하여, 모바일 앱이나 SPA 환경에서 OAuth 2.0 보안성을 극대화할 수 있습니다.