Skip to content

[P2] [서버 연동] 구글 로그인이 가끔 실패한다 — 구글이 PKCE code_verifier 불일치로 거절 (Invalid code verifier) #665

Closed

Description

@selee1012

증상

구글 로그인 버튼을 누르고 구글 계정을 고른 뒤 「로그인에 실패했어요」가 뜹니다. 서버가 인가 코드를 구글 토큰 엔드포인트로 교환할 때 구글이 거절합니다.

Google OAuth failed: status=400 BAD_REQUEST
{ "error": "invalid_grant", "error_description": "Invalid code verifier." }

→ 앱에는 400 LOGIN_FAILED 로 갑니다.

prod 에서 확인한 것 (30일, 2건)

시각 (KST) 앞뒤 흐름
10/30 20:12 POST /v1/auth/oauth/google 1회만 오고 실패
11/02 23:47 POST /v1/auth/oauth/google 1회만 오고 실패, 3분 뒤 구글 로그인 성공 기록 있음(같은 사용자인지는 확정 못 함)
  • Invalid code verifier 는 인가 코드를 받을 때 쓴 PKCE code_challenge 와, 서버로 보낸 codeVerifier 가 맞지 않을 때 구글이 돌려주는 값입니다. 코드가 만료됐거나 이미 쓴 코드면 다른 메시지(Bad Request/Malformed auth code)가 옵니다.
  • 서버는 앱이 보낸 codeVerifier 를 그대로 전달만 하므로, 앱이 인가 요청과 다른 verifier 를 보낸 경우입니다.

앱에서 확인해 주실 것

OAuthActivity.authorizeGoogle() 에서 인가 요청(AuthorizationRequest)과 결과를 짝짓는 부분이 의심됩니다.

  • googleRequest 는 savedInstanceState 에서 복원되고, 복원된 요청이 없으면 새 요청(새 verifier) 을 만들어 Custom Tab 을 다시 띄웁니다.
    • Custom Tab 이 떠 있는 동안 OAuthActivity 가 저장 상태 없이 다시 만들어지면(예: 로그인 화면이 OAuthActivity 를 한 번 더 띄움, 구글 버튼 연타) 요청이 두 개가 됩니다. 이때 먼저 띄운 탭의 결과(코드 A)가 나중 요청의 verifier B 와 짝지어질 수 있습니다.
    • 코드에는 이렇게 쓰여 있습니다: codeVerifier = request.codeVerifier. 결과 response.request.codeVerifier 가 아니라 현재 Activity 의 request 를 씁니다. 이 경로라면 AppAuth 응답에 실린 원래 요청(response.request)의 verifier 를 쓰는 편이 안전합니다.
  • 구글 버튼이 처리 중에도 다시 눌리는지(로그인 화면 쪽 중복 실행 방지)
  • 구글 로그인 또는 화면 전환 시 초기 화면으로 돌아가는 문제 조사 #646(구글 로그인 후 초기 화면으로 돌아감)과 같은 경로인지

재현이 어렵다면 아래를 Observability 로 남겨 주시면 서버 로그와 맞춰 볼 수 있습니다.

  • authorizeGoogle 진입 시 restoredRequest 유무
  • 응답의 response.request.codeVerifier 와 보내는 codeVerifier 의 일치 여부(값 말고 해시 앞 몇 자 또는 일치 true/false)
  • 앱 버전

서버 쪽

변경 없음. 구글 응답 본문은 서버 WARN 로그에 남습니다.

완료 기준

  • 서버 로그 Google OAuth failed ... Invalid code verifier 가 새 버전 보급 뒤 0 건.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2주요 사용 흐름·정보 누락·기능 및 체감 성능bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions