인증 코드를 탈취당해도 토큰은 못 가져간다 — PKCE가 숨겨둔 검증 키

헤드라인

최근 국내 서비스들에 보안 이슈가 줄줄이 터졌잖아요. 우리 회사도 예외는 아니었고, 전사 보안 점검이 시작됐어요. 요청 수 제한, 애플리케이션 키 관리, 로그 마스킹, 개인정보 보관 방법 등등. 기능적으로는 구글 연동 로그인과 2FA 강제가 추가 개발 대상이 됐는데, 이걸 건드리려면 먼저 OAuth 2.0으로 구현된 인증 서버를 제대로 이해해야 했습니다.

근데요, 이번엔 좀 다른 입장이었어요. 지금까지는 외부 서비스의 OAuth를 소비하는 클라이언트 쪽이었는데, 이번엔 OAuth 서버를 직접 운영하는 쪽이 된 거예요. 연동 서비스가 삼성, LG 같은 외부 IoT 플랫폼에서 자사 기기 연동을 지원하거든요. 인증 서버는 외부 플랫폼에서 계정을 인증하고, 제어하려는 기기의 소유자가 맞는지 확인하는 역할을 합니다.

외부 플랫폼에 ID/PW를 넘기면 안 되는 이유

핵심은 간단해요. 인증을 위해 연동 플랫폼에서 사용자 ID/PW를 직접 입력받으면 안 된다는 것.

보안 문제가 있어요. 유저 정보가 외부 서비스에 직접 노출되니까요. 그리고 제어권의 문제도 있습니다. 2FA를 추가하거나 로그인 방법을 바꿔야 할 때, 외부 서비스에 직접 의존되면 움직일 수가 없거든요.

OAuth는 이걸 해결해줘요. 계정 관리 측에서 제공한 페이지에서 유저 정보를 입력하게 하면서도, 인증된 외부 사용처로 토큰을 안전하게 전달할 수 있는 틀을 제공합니다.

OAuth 2.0 흐름 — 네 단계로 끝

처음엔 헷갈리는데, 한두 번 그려보면 이해돼요.

1. 외부 클라이언트에 고유한 Client ID와 Client Secret을 발급하고, 인증 성공 후 코드를 전달받을 리다이렉트 경로를 미리 등록한다. 2. 사용자가 로그인 페이지에서 인증에 성공하면 임시 인가 코드를 발급하고, 미리 등록된 리다이렉트 경로로 전달한다. 3. 코드를 전달받은 외부 서비스는 코드와 함께 자신의 Client ID, Client Secret을 인증 서버로 다시 제출한다. 4. 인증 서버는 이 정보들을 검증하고, 서비스 접근 토큰을 발급한다.

깔끔하죠? 외부 서비스는 사용자의 비밀번호를 절대 모르고, 인증 서버가 발급한 토큰만 들고 있으면 됩니다.

OAuth 2.0 인증 흐름 다이어그램

여기까지가 교과서적인 이야기예요.

인증 코드가 중간에 탈취되면?

위 흐름에서 놓치기 쉬운 위험 포인트가 남아 있어요. OAuth 서버에서 사용자로 전달한 인증 코드(Authorization Code)가 탈취되는 경우.

이 코드가 공격자 손에 들어가면? 공격자가 클라이언트 서버에 그 코드를 전달해서 토큰을 요청할 수 있게 돼요. 결국 토큰 탈취.

이걸 방어하는 방법이 PKCE(Proof Key for Code Exchange)예요. 원리는 의외로 직관적입니다.

1. 최초 로그인 요청 시, 클라이언트가 임의의 값(code_verifier)을 생성하고 자기만 갖고 있는다. 2. OAuth 코드를 요청할 때, 이 값을 해싱한 것(code_challenge)만 서버에 보낸다. 3. 나중에 토큰을 요청할 때는 원본 값(code_verifier)을 보낸다. 4. OAuth 서버가 같은 방식으로 해싱해서 이전에 받은 값과 비교한다. 일치하면 코드 요청자와 토큰 요청자가 동일인물.

OAuth 2.0 + PKCE 방어 흐름

해커가 해싱된 값을 탈취해도 원본은 알 수 없으니까, 동일 사용자인 척하는 건 불가능하다는 거예요. (단방향 해시의 힘이죠.)

열쇠는 보내지 않고, 자물쇠만 먼저 보낸다

정리하면 이런 구조예요. 인가 코드를 요청할 때는 해싱한 값으로 원본을 노출하지 않은 채 소통이 오가고, 최종적으로 토큰을 요청할 때 원본 값을 전달해서 OAuth 서버가 검증하는 것. "자물쇠를 먼저 보내고, 열쇠는 나중에 보낸다"로 비유할 수 있겠네요.

보안 점검에서 PKCE가 빠져 있는 OAuth 구현을 발견했다면 — 그건 문이 열려 있는 거나 마찬가지예요. 코드 탈취 공격에 대한 방어선이 없다는 뜻이니까요.

결국 보안이라는 게, 하나를 지키면 된다는 게 아니라 빈틈을 하나씩 메꾸는 과정이더라고요. 요청 수 제한, 키 관리, 로그 마스킹, 개인정보 보관, 구글 연동 로그인, 2FA 강제 — 이 목록 끝에 PKCE가 있었습니다.