PKCE가 왜 필요한가
OAuth 2.0 인증 코드 흐름(Authorization Code Flow)은 원래 서버 환경을 기준으로 설계됐습니다. 서버는 ‘client secret’이라는 비밀값을 안전하게 보관할 수 있지만, 모바일 앱이나 SPA(싱글 페이지 앱)는 다릅니다. 앱을 디컴파일하면 코드 안에 박아둔 secret이 그대로 노출되기 때문에, 이 값으로 신원을 증명하는 방식은 사실상 무의미해집니다. 게다가 모바일 환경에서는 인증 서버가 앱으로 돌려주는 ‘authorization code’를 커스텀 URL 스킴 같은 취약한 경로로 전달하는데, 이 과정에서 악성 앱이 같은 스킴을 등록해 코드를 가로챌 수도 있습니다. PKCE(Proof Key for Code Exchange, 발음은 ‘픽시’)는 바로 이 두 가지 문제, 즉 secret을 안전하게 둘 수 없는 문제와 code 탈취 문제를 해결하기 위해 IETF가 표준화한 확장 사양(RFC 7636)입니다.
동작 원리: 열쇠와 자물쇠 비유
PKCE의 핵심은 ‘매번 다른 임시 열쇠를 만들어서, 시작할 때는 자물쇠만 보여주고 끝날 때 진짜 열쇠로 검증받는 것’입니다. 흐름은 다음과 같습니다.
- 앱이 무작위 문자열인 ‘code_verifier’를 생성합니다 (예: 43~128자의 랜덤 값)
- 이 값을 SHA-256으로 해시한 뒤 Base64url로 인코딩해 ‘code_challenge’를 만듭니다
- 인증 요청을 보낼 때 code_challenge만 서버에 전달합니다 (자물쇠만 보여주는 단계)
- 사용자 로그인 후 서버가 authorization code를 돌려주면, 앱은 토큰 교환 요청 시 원본 code_verifier를 함께 보냅니다
- 서버는 저장해둔 code_challenge와 지금 받은 code_verifier를 해시해서 비교하고, 일치할 때만 토큰을 발급합니다
만약 중간에 authorization code를 가로챈 공격자가 있다 해도, 원본 code_verifier를 모르면 토큰 교환 단계에서 검증에 실패합니다. 즉 code_verifier는 오직 앱 내부 메모리에만 존재하는 일회용 증명 값 역할을 합니다.
실무에서 왜 반드시 써야 하나
OAuth 2.1 초안부터는 PKCE가 모든 클라이언트 유형에 필수로 권고되고 있습니다. 예전에는 ‘공개 클라이언트(모바일, SPA)만 쓰면 된다’는 인식이 있었지만, 지금은 서버 기반 앱에서도 코드 가로채기 공격을 막기 위해 기본으로 적용하는 추세입니다. 실제로 Google, Auth0, Okta 같은 주요 OAuth 제공자들은 모바일 SDK에서 PKCE를 기본값으로 강제하거나 강력히 권장합니다. 구현 자체는 어렵지 않아서 대부분의 OAuth 클라이언트 라이브러리(예: AppAuth)가 code_verifier 생성과 해시 처리를 자동으로 해줍니다. 결론적으로 PKCE는 ‘추가 옵션’이 아니라 모바일 앱에서 OAuth를 쓸 때 지켜야 할 최소한의 보안 기본기라고 이해하는 것이 안전합니다.