← 블로그

GitHub Actions에서 Google OAuth Refresh Token 만료 오류 해결하기

GitHub Actions에서 Google OAuth Refresh Token 만료 오류 해결하기

Blogger에 글을 자동으로 발행하는 GitHub Actions workflow가 갑자기 실패했습니다.

기존에는 정상적으로 동작하던 자동화였기 때문에 처음에는 GitHub Actions 문제이거나 코드 오류라고 생각했습니다. 하지만 로그를 확인해보니 원인은 Blogger API 호출 이전 단계, 정확히는 Google OAuth refresh token 만료였습니다.

GitHub Actions 실패 실행

발생한 오류

GitHub Actions 로그에는 다음 메시지가 출력되었습니다.

Google OAuth token refresh failed: 400 Token has been expired or revoked.

Google OAuth token refresh 실패 로그

workflow는 Blogger API를 호출하기 전에 Google OAuth refresh token으로 access token을 새로 발급받습니다.

그런데 refresh token 자체가 만료되었거나 폐기된 상태라면 access token을 만들 수 없습니다. 이 경우 Blogger API 요청까지 가지 못하고 인증 단계에서 바로 실패합니다.

처음에는 일시적인 인증 실패일 가능성도 생각했습니다. 하지만 Token has been expired or revoked 메시지는 단순 네트워크 오류라기보다 refresh token이 더 이상 유효하지 않다는 뜻에 가깝습니다.

원인 확인

Google Cloud Console을 확인해보니 OAuth 앱의 게시 상태가 테스트 상태였습니다.

Google OAuth 앱 테스트 상태

Google OAuth 앱이 테스트 상태인 경우, 특정 scope를 사용하는 refresh token은 일정 기간 후 만료될 수 있습니다. 그래서 자동화가 며칠 동안은 정상적으로 동작하다가 어느 순간 갑자기 실패한 것입니다.

이번 문제의 핵심은 GitHub Actions 자체가 아니라 Google OAuth 앱의 게시 상태였습니다.

해결 방법

해결은 두 단계로 진행했습니다.

첫 번째는 Google Cloud Console에서 OAuth 앱을 프로덕션 상태로 변경하는 것입니다.

Google Cloud Console
> APIs & Services
> OAuth consent screen 또는 Audience
> Publishing status 변경

테스트 상태에서 프로덕션 상태로 변경하면 화면에 프로덕션 단계로 표시됩니다.

Google OAuth 앱 프로덕션 상태

여기서 중요한 점은 기존 refresh token을 그대로 쓰면 안 된다는 것입니다. 게시 상태를 변경한 뒤 새 refresh token을 다시 발급해야 합니다.

두 번째는 프로젝트에서 Blogger OAuth 설정 명령을 다시 실행하는 것입니다.

npm run setup:blogger

이 명령을 실행하면 Google 인증 URL이 출력됩니다. 해당 URL에 접속해서 Blogger 계정으로 권한을 허용하면 새 refresh token이 출력됩니다.

발급받은 refresh token은 GitHub 저장소의 Actions Secret에 다시 저장했습니다.

GitHub Repository
> Settings
> Secrets and variables
> Actions
> Repository secrets
> GOOGLE_REFRESH_TOKEN

GitHub Actions Secret의 GOOGLE_REFRESH_TOKEN 갱신

Secret 값은 화면에 직접 노출되지 않지만, 캡처할 때도 값이 보이는 화면은 피하는 것이 좋습니다.

코드도 함께 개선했습니다

이번 문제의 직접 원인은 refresh token 만료였습니다. 다만 기존 코드는 이런 영구적인 인증 실패도 여러 번 재시도하고 있었습니다.

refresh token이 이미 만료되었거나 폐기된 상태라면 같은 요청을 다시 보내도 성공할 수 없습니다. 그런데 기존에는 동일한 오류를 몇 번 반복한 뒤에야 실패했습니다.

그래서 Blogger OAuth token 갱신 중 expired or revoked 같은 영구 실패가 발생하면 즉시 실패하도록 코드를 개선했습니다.

수정한 주요 내용은 다음과 같습니다.

- Google OAuth token 오류를 별도 에러 클래스로 분리
- expired or revoked 오류를 영구 실패로 판단
- retry 유틸에 shouldRetry 옵션 추가
- 영구 실패인 경우 불필요한 재시도를 중단

이렇게 해두면 다음에 같은 문제가 생겼을 때 GitHub Actions 로그를 더 빠르게 이해할 수 있습니다.

검증

코드 수정 후에는 타입 체크와 테스트를 모두 실행했습니다.

npm run typecheck
npm test

결과는 모두 통과했습니다.

typecheck 통과
9개 테스트 통과

이후 변경 사항을 커밋하고 GitHub에 푸시했습니다.

fix: stop retrying revoked Blogger OAuth tokens

새 refresh token을 GitHub Secrets에 반영한 뒤 workflow도 정상적으로 다시 실행되었습니다.

GitHub Actions 성공 실행

정리

이번 문제의 핵심은 GitHub Actions 자체가 아니라 Google OAuth 앱의 게시 상태였습니다.

OAuth 앱이 테스트 상태이면 refresh token이 짧은 기간 후 만료될 수 있습니다. 자동화 작업처럼 장기간 반복 실행되어야 하는 경우에는 OAuth 앱을 프로덕션 상태로 변경하고, 그 이후 새 refresh token을 발급받아 GitHub Secrets에 다시 등록해야 합니다.

이번에 적용한 최종 조치는 다음과 같습니다.

1. Google OAuth 앱을 테스트 상태에서 프로덕션 상태로 변경
2. refresh token 재발급
3. GitHub Secrets의 GOOGLE_REFRESH_TOKEN 교체
4. Blogger OAuth 영구 실패 시 불필요한 재시도 중단
5. 타입 체크 및 테스트 통과 후 커밋/푸시

앞으로 같은 오류가 발생한다면 먼저 GitHub Actions 로그에서 아래 메시지를 확인하면 됩니다.

Token has been expired or revoked

이 메시지가 보인다면 refresh token을 새로 발급하고 GitHub Secret을 업데이트하는 것이 우선입니다.