🦎

Google OAuth 구현 가이드 (서버 중심, PKCE 기반)

최민석·2025-02-19

Google OAuth 구현 가이드 (서버 중심, PKCE 기반)

개발자를 위한 Google OAuth 인증 흐름 및 보안 설계 원칙


목표

  • 기존 구현방식 문제점
  • PKCE란?
  • Android / iOS 클라이언트에서 Google OAuth 로그인 처리
  • 서버에서 모든 민감 정보 직접 검증
  • id_token, access_token을 안전하게 수신 및 활용

기존 클라이언트 의존 방식의 문제점

클라이언트에서 id_token, access_token을 직접 받아 서버로 전달하는 방식

문제점:

  1. 보안 취약성
    • 탈옥/루팅된 기기에서 토큰이 탈취될 가능성이 있음
    • access_token이 유출되면 Google API에 무단 접근 가능
  2. 서버 검증 신뢰도 저하
    • 서버는 id_token을 직접 발급받은 게 아니기 때문에 위조 여부를 확실히 판단하기 어려움
  3. OAuth 흐름의 권장 방식이 아님
    • OAuth 2.0 표준에서는 민감한 토큰 교환을 서버에서 처리하는 Authorization Code Flow + PKCE 방식을 권장
  4. 관리 어려움
    • 토큰 만료나 갱신 등의 로직을 클라이언트에 두게 되면 유지보수가 어려워짐

서버 중심 구조로 전환해야 하는 이유

Authorization Code + PKCE 방식으로, 클라이언트는 code만 받고, 토큰은 서버가 교환

장점:

  1. 민감한 토큰은 서버에만 존재 → 노출 위험 최소화
  2. id_token의 서명, 발급자, 유효기간 등을 서버에서 정밀하게 검증 가능
  3. Google API 접근 시 access_token을 서버에서 안전하게 활용 가능
  4. OAuth 2.0의 표준 흐름을 따르므로 유지보수 및 확장성에 유리

PKCE와 code_verifier, code_challenge 관계 정리


1. PKCE란?

**PKCE(픽시)**는

“클라이언트 시크릿을 사용할 수 없는 앱(예: 모바일, SPA 등)”

안전하게 OAuth 인증을 수행하기 위해 고안된 보안 메커니즘

원래 OAuth2의 Authorization Code Flow는 클라이언트 시크릿을 요구했지만, 모바일 앱은 이 시크릿을 안전하게 숨길 수 없어, 대신 PKCE로 code_verifier와 code_challenge를 사용해 인증 요청의 정당성을 검증함.


2. 주요 개념

항목 설명
code_verifier 앱이 생성한 무작위 문자열 (비밀번호처럼 생각하면 됨)
code_challenge code_verifier를 SHA-256으로 해싱한 후, base64-url 인코딩한 값
code_challenge_method 보통 S256 (SHA-256 기반) 사용

3. PKCE 전체 흐름

[1단계] 클라이언트가 인증 요청

앱 → 구글 로그인 페이지 요청 시:
  - code_challenge
  - code_challenge_method=S256

[2단계] 사용자가 로그인하고 Authorization Code 수신

구글 로그인 완료 → 앱은 code만 받음

[3단계] 앱이 서버에 전달

앱 → 서버로 다음 전송:
  - code
  - code_verifier
  - osType (ANDROID or IOS)

osType은 구글 소셜 로그인에서만 필요한 값으로, ANDRIOD용 ClientId와 IOS용 ClientId가 나뉘어있기 때문이다. 그러나 이는 어떤 SDK를 사용하느냐, 어떻게 통합하는지에 따라 다르므로, Web application을 쓸 때도 있다. Google에서 제공하는 통신 규격 확인 후 선택하기 바란다.

3 1

[4단계] 서버가 Google에 토큰 요청

POST /token
  - code
  - code_verifier

🔐 Google은 자체적으로 code_verifier로 code_challenge를 재생성해,

초기 요청에서 전달받은 code_challenge와 비교하여 동일하면 access_token, id_token을 발급한다.


왜 안전한가?

단순히 authorization code만 탈취해서는 토큰을 못 받기 때문

  • 공격자가 code를 중간에서 탈취하더라도, code_verifier는 앱 내부에만 있기 때문에, 토큰 요청은 실패함

정리 요약

개념 역할
code_verifier 토큰 교환 시 증명자료 (비밀번호 같은 것)
code_challenge 구글 서버가 사전에 받아놓는 검증용 해시
PKCE 이 둘을 통해 authorization code 탈취 공격을 방어하는 OAuth 확장 기법

개선 후 전체 인증 흐름 (시퀀스 다이어그램)

oauth 1

이때 서비스 성격에 따라 완전 회원가입을 시키는 대신, 임시 유저로 넘겨서 소셜정보로 완벽히 채울 수 없는 부분을 다 채워야 할 수 있다.


각 단계별 설명

단계 주체 요청/응답 설명
(1) 앱 클라이언트 → Google OAuth 요청 (client_id, code_challenge, redirect_uri, scope, response_type=code) PKCE 사용하여 보안 강화
(2) Google → 사용자 로그인 UI 사용자 계정 및 권한 동의
(3) Google → 앱 클라이언트 Authorization Code 일회용 코드
(4) 앱 클라이언트 → 앱 서버 code, code_verifier, osType 포함 POST 요청 클라이언트가 받은 code를 서버에 위임
(5) 앱 서버 → Google client_id, client_secret(optional), code, code_verifier, grant_type=authorization_code Google OAuth 서버에 토큰 요청
(6) Google → 앱 서버 access_token, id_token, expires_in, token_type 등 access_token은 Google API용, id_token은 사용자 인증용
(7) 앱 서버 내부 id_token의 aud, iss, exp 검증 신뢰성 검증
(8) 앱 서버 내부 유저 회원가입 or 로그인 처리 유저 정보 업데이트 등
(9) 앱 서버 → 앱 클라이언트 우리 시스템의 JWT 토큰 발급 앱에서는 이 토큰으로 로그인 완료 처리

id_token, access_token 차이

항목 목적 용도 노출 여부
id_token 사용자의 신원 확인 Google이 발급한 JWT (sub, email 포함) 서버에서 검증 필요
access_token Google API 호출 Google People API 등 접근 가능 노출되면 위험

왜 서버 수신 방식이 안전한가?

  • 모바일 클라이언트에 민감 토큰을 노출하지 않음
  • id_token을 신뢰할 수 있는 서버에서만 검증
  • Google API 호출이 서버에서 이루어짐 (보안적 제어)

주의 사항

  • 클라이언트에서 osType (ANDROID / IOS) 값을 함께 보내야 서버가 적절한 Client ID로 aud를 검증할 수 있음
  • id_token 검증 시 반드시 aud, iss, exp 체크할 것

추가 참고 문서

PKCE 딥다이브: Authorization Code 탈취를 어떻게 방어하는가