Google OAuth DPoP, 유출된 토큰을 어디까지 막을까?

Google OAuth 요청에 DPoP 서명을 붙였는데 응답의 토큰 종류는 여전히 Bearer다. DPoP가 실제로 묶어 두는 토큰은 어느 쪽일까?

Google OAuth DPoP, 유출된 토큰을 어디까지 막을까?
세 줄 요약
  • Google의 DPoP는 새 액세스 토큰을 발급받는 데 쓰는 리프레시 토큰을 보호한다.
  • 키에 묶인 리프레시 토큰으로 액세스 토큰을 갱신할 때마다 같은 개인 키로 새 증명을 만들어야 한다.
  • 기존 사용자의 토큰 전환과 nonce 오류 처리는 앱 구현에 포함해야 한다.

토큰만 복사해서 새 액세스 토큰을 받는 길을 막는다

사용자가 자리를 비운 동안에도 Google Drive나 Calendar에 접근하는 앱은 리프레시 토큰으로 액세스 토큰을 갱신한다. Google이 10월 15일 소개한 DPoP 지원은 이 갱신 권한을 클라이언트가 가진 키에 묶는다. 공격자가 토큰 문자열만 복사해도 해당 개인 키로 서명하지 못하면 사용할 수 없다.

Google API에 보내는 액세스 토큰은 여전히 Bearer다. Google은 API 호출마다 추가 서명 검증을 요구하는 비용을 피하면서 장기 자격증명을 보호하는 설계라고 설명한다. 그래서 DPoP를 붙인 뒤에도 응답의 token_type이 Bearer인 것은 예상된 결과다.

대상Google DPoP 적용 뒤남는 과제
DPoP로 발급한 리프레시 토큰키 소유 증명이 있어야 갱신 가능하다개인 키도 보호해야 한다
발급된 액세스 토큰기존 Bearer 방식이다유출 방지와 토큰 폐기가 필요하다
웹 로그인 세션 쿠키이 기능의 보호 대상이 아니다웹 세션 보호를 따로 구현해야 한다

DPoP 표준도 Bearer 액세스 토큰을 발급하는 선택을 허용한다. DPoP라는 이름만 보고 액세스 토큰과 로그인 쿠키까지 모두 키에 묶였다고 판단할 수는 없다.

첫 발급과 이후 갱신 요청에 서명한 증명을 더한다

앱은 비대칭 키쌍을 만들고 Google 토큰 엔드포인트에 보내는 요청의 DPoP 헤더에 서명된 JWT를 넣는다. Google 도입 가이드는 EC P-256과 ES256을 사용한다. 공개 키 정보는 증명에 포함하고 개인 키는 클라이언트 쪽에 남긴다.

최초 인가 코드를 교환할 때 키에 묶인 리프레시 토큰을 받고, 이후에는 같은 키로 매 요청의 증명을 새로 만든다. 웹 서버용 문서에서 특히 구별해야 할 값은 jti다. 인가 코드 교환에서는 코드의 SHA-256 해시를 Base64URL로 인코딩하고, 리프레시 토큰 교환에서는 요청마다 고유한 식별자를 쓴다.

요청 메서드인 htm, 토큰 엔드포인트 주소인 htu, 발급 시각 iat도 요청에 맞아야 한다. 한 번 만든 JWT를 계속 붙이는 방식은 맞지 않는다.

기존 토큰은 새로 발급받는 전환이 필요하다

이미 저장한 일반 리프레시 토큰을 그대로 두고 전체 사용자가 보호됐다고 표시해서는 안 된다. Google의 기존 사용자 안내는 새 DPoP 토큰을 받으려면 사용자가 기존 권한 부여를 취소하거나, 최초 인가 요청에 prompt=consent를 넣어 새 동의를 받아야 한다고 명시한다. 웹 서버 앱이 리프레시 토큰을 받으려면 access_type=offline도 필요하다.

프런트엔드만 있는 SPA라면 구조도 확인해야 한다. 현재 도입 가이드는 client secret과 DPoP-Nonce 헤더의 CORS 제약 때문에, 비밀값을 보관하지 않는 순수 SPA의 직접 사용 대신 BFF를 경유하도록 안내한다. BFF는 브라우저 요청을 받아 토큰 처리를 맡는 서버다.

use_dpop_nonce는 새 증명으로 다시 요청하라는 신호다

nonce 교환 규칙에 따라 400 응답의 오류가 use_dpop_nonce라면, 응답 헤더의 새 DPoP-Nonce 값을 읽어 새 증명에 넣고 다시 요청한다. 이미 서명한 JWT에 문자열만 바꾸는 것이 아니라 다시 서명해야 한다.

invalid_dpop_proof라면 Google 문서가 지정한 메서드·주소·시각·중복 식별자·서명을 점검한다. 모든 갱신 실패를 사용자 재로그인으로 넘기는 대신 오류 종류에 맞는 처리가 필요하다.

개인 키와 토큰이 함께 노출되면 토큰만 유출됐을 때의 보호 조건이 성립하지 않는다. Google의 보관 원칙대로 서버의 사용자 토큰은 저장 시 암호화하고, 필요 없어진 토큰은 취소·삭제해야 한다. 인가 코드 교환을 보호하는 PKCE도 유지한다. DPoP를 도입할 때는 새 헤더뿐 아니라 키 보관, 기존 사용자 전환, 갱신 오류 처리까지 한 흐름으로 구현해야 한다.