← 목록으로AI WORKFLOW

Claude Code로 30분 만에 붙이는 Supabase 구글 소셜 로그인 (OAuth 설정 전 과정)

Claude Code로 30분 만에 붙이는 Supabase 구글 소셜 로그인 (OAuth 설정 전 과정)

구글 로그인을 맨손으로 구현하면 할 일이 생각보다 많습니다. authorization code 받고, 토큰 엔드포인트에 client_secret 실어서 교환하고, id_token 검증하고, 자체 세션 발급하고. 참고 자료를 보면 이 교환 로직만으로 Route Handler 하나가 가득 찹니다.

Supabase를 백엔드로 쓴다면 이 과정 전부가 이미 만들어져 있습니다. 남는 일은 구글 콘솔 설정과 클라이언트 코드 몇 줄인데, 그 몇 줄마저 Claude Code한테 시켰습니다. 아래는 그 과정의 기록입니다. 전체 인프라 구성은 1인 개발자를 위한 최소 인프라 — Vercel + Supabase 구성기에 정리해 뒀고, 이 글은 그 구성 위에 Auth를 얹는 편입니다.

전체 그림: 토큰 교환 서버를 내가 안 만든다

직접 구현할 때의 흐름은 이렇습니다.

  1. 클라이언트가 accounts.google.com으로 이동해 authorization code를 받고
  2. 서버가 code + client_secret으로 oauth2.googleapis.com/token에서 id_token을 교환하고
  3. id_token을 검증해 자체 access token을 발급

Supabase Auth를 쓰면 2번과 3번이 통째로 Supabase 몫이 됩니다. 구글이 코드를 돌려주는 곳이 내 서버가 아니라 https://<project-ref>.supabase.co/auth/v1/callback입니다. 교환과 세션 발급이 끝나면 Supabase가 내 앱으로 다시 리다이렉트해 줍니다. client_secret이 내 코드베이스에 아예 없다는 게 부수 효과이자 사실상 본 효과입니다.

구글 OAuth 로그인 흐름 — 앱에서 Google 인증을 거쳐 Supabase가 세션을 만들고 다시 앱으로 돌아온다

Google Cloud Console 설정 — 5분, 대신 정확히

코드보다 이쪽이 먼저입니다. 여기서 틀리면 코드가 아무리 맞아도 redirect_uri_mismatch만 봅니다.

  1. console.cloud.google.com에서 프로젝트 생성 → OAuth 동의 화면 구성. User Type은 외부(External), 개발 중엔 테스트 사용자로 내 계정만 등록해 두면 됩니다.
  2. 사용자 인증 정보 → OAuth 클라이언트 ID 생성 → 유형은 웹 애플리케이션.
  3. 승인된 리디렉션 URI에 https://<project-ref>.supabase.co/auth/v1/callback 입력. 내 앱 주소가 아니라 Supabase 콜백 주소입니다. (Supabase 공식 가이드)
  4. 발급된 Client ID와 Secret을 Supabase 대시보드 → Authentication → Providers → Google에 붙여넣고 Enable.

<project-ref>는 Supabase 프로젝트 설정의 Reference ID입니다. Google 프로바이더 설정 화면에 콜백 주소 전체가 그대로 나오니 복사해서 쓰면 됩니다.

Claude Code에 시킨 부분

콘솔 설정을 끝내고 Claude Code에 이렇게 요청했습니다.

Supabase Auth로 구글 소셜 로그인을 붙여줘.
- 로그인 버튼 → signInWithOAuth({ provider: "google" })
- /auth/callback 라우트에서 세션 교환 처리
- 로그인 상태는 onAuthStateChange로 구독

CLAUDE.md에 Supabase를 쓴다는 컨텍스트가 이미 있어서 생성된 코드가 프로젝트 구조에 바로 맞았습니다. 핵심 코드는 결국 이 정도입니다.

// 로그인 버튼
await supabase.auth.signInWithOAuth({
  provider: "google",
  options: { redirectTo: `${location.origin}/auth/callback` },
});
// 로그인 상태 구독
supabase.auth.onAuthStateChange((_event, session) => {
  setUser(session?.user ?? null);
});

직접 구현 버전에서 Route Handler를 채우던 토큰 교환·쿠키 저장 코드가 전부 사라졌습니다. 검증 포인트는 두 개만 남습니다. redirectTo가 실제 존재하는 라우트인지, 그리고 그 라우트에서 세션이 잡히는지.

여기서 데인 것들

redirect URI가 두 종류라는 것. 구글 콘솔에 등록하는 건 Supabase 콜백 주소, signInWithOAuthredirectTo는 내 앱 주소입니다. 이걸 반대로 넣으면 각각 redirect_uri_mismatch와 “로그인은 됐는데 이상한 데로 떨어지는” 증상이 나옵니다. 에러 메시지가 서로 달라서 원인을 한 번에 못 잡았습니다.

Site URL이 localhost로 남아 있는 것. (리디렉션 URL 설정 문서) Supabase 대시보드 → Authentication → URL Configuration의 Site URL이 기본값(http://localhost:3000)인 채로 배포하면, 프로덕션에서 로그인한 사용자가 localhost로 리다이렉트됩니다. 배포 도메인으로 바꾸고, 로컬 개발용 주소는 Additional Redirect URLs에 따로 추가하는 게 맞는 구성입니다. 배포 도메인 자체를 붙이는 과정은 Cloudflare Pages 커스텀 도메인 연결에 정리해 뒀습니다.

테스트 모드의 조용한 차단. OAuth 동의 화면이 테스트 상태면 등록된 테스트 사용자 외에는 로그인 자체가 안 됩니다. 내 계정으론 되는데 남 계정으론 안 된다면 코드가 아니라 이 상태부터 확인하세요.

RLS와 만나는 지점

로그인이 붙는 순간부터 이전 글에서 정리한 RLS 정책의 auth.uid()에 실제 값이 들어오기 시작합니다. 정책은 이미 테이블에 선언돼 있으니 로그인만 붙이면 “내 데이터만 보이는” 동작이 API 코드 없이 완성됩니다. Auth를 먼저 붙이고 RLS를 나중에 고민하는 순서보다, RLS를 깔아두고 Auth를 얹는 순서가 결과적으로 코드가 적었습니다.

다음은 카카오

같은 프로젝트에 카카오 로그인도 이어서 붙였는데, 코드는 provider만 바꾸면 되는 반면 콘솔 쪽 관문이 하나 더 있었습니다. 이메일을 받으려면 심사를 통과해야 한다는 점이요. 그 기록은 카카오 로그인 글에 정리했습니다.

결론

구글 소셜 로그인에서 시간을 잡아먹는 건 코드가 아니라 설정입니다. 코드는 Claude Code가 몇 분 만에 생성했고 실제로 걸린 시간의 대부분은 구글 콘솔과 Supabase 대시보드 사이를 오가는 데 들어갔습니다. 그러니 순서를 이렇게 잡는 게 맞습니다. 콘솔 설정을 먼저 정확히 끝내고, 코드는 그다음에. 반대로 하면 코드를 의심하면서 설정 오류를 찾게 됩니다.