TELEPASI

검색하기 전에 통하다

Self-hosted Supabase Google 로그인 설정 (3/3)

강병우
2026.07.18 👁️ 88

Self-hosted Supabase Google 로그인 설정 (3/3) - Web 연동과 테스트

시리즈 목차
Self-hosted Supabase Google 로그인 설정 (1/3) - 개념과 Google Cloud 설정
Self-hosted Supabase Google 로그인 설정 (2/3) - 서버 설정
Self-hosted Supabase Google 로그인 설정 (3/3) - Web 연동과 테스트 (현재 글)

관련 문서
Self-hosted Supabase API 도메인 연결
Google 소셜 로그인 문제 해결 및 참고자료

1편 요약: Web, API, Server 개념을 이해하고 Google Cloud Console에서 OAuth 클라이언트를 생성했습니다.

2편 요약: Backend 서버의 .env에 Google Client 값을 설정하고 Auth 컨테이너를 재생성했습니다.

**3편(현재 글)**에서는 Frontend Web 애플리케이션에서 Google 로그인을 연동하고 처음부터 끝까지 테스트합니다.


예시 환경

  • Web 애플리케이션: https://example.com
  • Supabase 외부 API: https://sb.example.com
  • Frontend 프로젝트 경로: /var/www/example.com/app (예시)
  • Web Framework: SvelteKit (예시)

중요: 이 문서의 example.com은 예제 도메인입니다. 실제 설정할 때는 자신이 소유한 도메인으로 모두 바꿔야 합니다.


사전 준비 확인

3편을 진행하기 전에 다음 사항을 확인합니다.

필수 완료 사항:

  • Google Cloud에서 OAuth 클라이언트 생성 완료 (1편)
  • Backend 서버 .env와 Auth 컨테이너 설정 완료 (2편)
  • Auth 설정 API에서 Google provider 활성화 확인 (2편)
  • 브라우저에서 https://sb.example.com 접속 가능

1. Frontend와 Backend의 구분

Frontend 프로젝트에도 .env 파일이 있을 수 있지만, Backend 서버의 .env와는 완전히 다른 파일입니다.

[Backend 서버]
/root/supabase/docker/.env
→ Google Client Secret 저장
→ Auth 컨테이너가 읽음
                │
                │ API 호출
                ▼
[Frontend 프로젝트]
/var/www/example.com/app/.env
→ Supabase public URL과 anon key만 저장
→ 브라우저용 Supabase Client가 사용

Frontend .env에는 절대로 다음 값을 넣지 않습니다.

  • Google Client Secret
  • Supabase SERVICE_ROLE_KEY
  • 데이터베이스 비밀번호

2. 브라우저용 Supabase Client 설정

2.1 Frontend 프로젝트의 .env

Frontend 프로젝트의 .env에는 공개 Supabase URL과 anon/publishable key만 저장합니다.

SvelteKit 예시:

# Frontend 프로젝트의 .env
PUBLIC_SUPABASE_URL=https://sb.example.com
PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_OR_PUBLISHABLE_KEY

프레임워크별 공개 환경 변수 접두사:

  • SvelteKit: PUBLIC_
  • Vite: VITE_
  • Next.js: NEXT_PUBLIC_

주의: 공개 접두사가 붙은 값은 최종 JavaScript에 포함되어 브라우저에서 볼 수 있다고 가정해야 합니다.

2.2 Supabase Client 생성

SvelteKit 코드 예시:

import { createClient } from '@supabase/supabase-js'
import {
  PUBLIC_SUPABASE_URL,
  PUBLIC_SUPABASE_ANON_KEY,
} from '$env/static/public'

const supabase = createClient(
  PUBLIC_SUPABASE_URL,
  PUBLIC_SUPABASE_ANON_KEY,
  {
    auth: {
      flowType: 'pkce',
      persistSession: true,
      autoRefreshToken: true,
      detectSessionInUrl: true,
    },
  },
)

설정 옵션:

  • flowType: 'pkce': 보안이 강화된 PKCE 흐름 사용
  • persistSession: true: 브라우저 저장소에 세션 유지
  • autoRefreshToken: true: 토큰 자동 갱신
  • detectSessionInUrl: true: URL의 OAuth 코드 자동 처리

3. Google 로그인 시작

3.1 로그인 버튼 클릭 시 호출

async function handleGoogleLogin() {
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: 'google',
    options: {
      redirectTo: `${window.location.origin}/auth/callback`,
    },
  })

  if (error) {
    console.error('Google 로그인 시작 실패:', error.message)
    return
  }

  if (data?.url) {
    window.location.href = data.url
  }
}

3.2 redirectTo 주소 주의

여기의 redirectToGoogle Cloud의 승인된 리디렉션 URI가 아닙니다. 로그인 완료 후 돌아올 Web 복귀 주소입니다.

Web redirectTo (여기서 설정):
https://example.com/auth/callback

Google 승인된 리디렉션 URI (1편에서 설정):
https://sb.example.com/auth/v1/callback

두 주소는 역할이 완전히 다릅니다.

  • sb.example.com/auth/v1/callback: Google → Supabase Auth
  • example.com/auth/callback: Supabase → Web 애플리케이션

4. Web 콜백에서 PKCE 세션 교환

4.1 /auth/callback 페이지 구현

SvelteKit, Next.js처럼 PKCE 흐름을 사용하는 애플리케이션은 Web의 /auth/callback에서 쿼리의 code를 Supabase 세션으로 교환합니다.

// /auth/callback 페이지 (예: src/routes/auth/callback/+page.svelte)
import { onMount } from 'svelte'
import { goto } from '$app/navigation'
import { supabase } from '$lib/supabaseClient'

onMount(async () => {
  const url = new URL(window.location.href)
  const code = url.searchParams.get('code')

  if (code) {
    const { data, error } = await supabase.auth.exchangeCodeForSession(url.href)

    if (error) {
      console.error('세션 교환 실패:', error.message)
      goto('/login?error=auth_failed')
      return
    }

    console.log('로그인 사용자:', data.session?.user)
    goto('/dashboard') // 로그인 후 이동할 페이지
  } else {
    goto('/login')
  }
})

4.2 detectSessionInUrl 사용 시 주의

프로젝트에서 detectSessionInUrl: true를 사용하는 경우 라이브러리가 먼저 code를 처리할 수 있습니다. 같은 code를 두 번 교환하지 않도록 현재 URL에 code가 남아 있는 경우에만 직접 교환합니다.

4.3 SSR 환경

SSR에서 세션을 쿠키에 저장한다면 브라우저용 Client가 아니라 프레임워크의 서버용 Supabase Client와 공식 SSR 가이드를 적용해야 합니다.


5. 처음부터 끝까지 테스트

브라우저 시크릿 창에서 테스트하면 기존 세션과 캐시의 영향을 줄일 수 있습니다.

5.1 전체 흐름 확인

  1. https://example.com의 로그인 페이지를 엽니다.
  2. Google 로그인 버튼을 누릅니다.
  3. 브라우저 주소가 sb.example.com/auth/v1/authorize를 거치는지 확인합니다.
  4. Google 계정 선택 또는 동의 화면이 나타나는지 확인합니다.
  5. Google 인증 후 sb.example.com/auth/v1/callback을 거치는지 확인합니다.
  6. 마지막으로 example.com/auth/callback에 도착하는지 확인합니다.
  7. Web에서 로그인 세션과 이메일을 확인합니다.
  8. Supabase auth.users에 사용자가 생성되었는지 확인합니다.

5.2 로그인 세션 확인

Frontend 코드에서 현재 사용자 확인:

const { data: { user } } = await supabase.auth.getUser()

if (user) {
  console.log('로그인된 사용자:', user.email)
} else {
  console.log('로그인되지 않음')
}

5.3 DB 사용자 확인

Backend 서버에서 Supabase 데이터베이스 확인:

docker exec supabase-db psql -U postgres -d postgres -c \
  "SELECT id, email, created_at FROM auth.users ORDER BY created_at DESC LIMIT 5;"

Google이 제공한 이름과 프로필 이미지는 범위와 설정에 따라 raw_user_meta_data에 저장될 수 있습니다.


6. 일반적인 오류와 해결

6.1 redirect_to is not allowed

원인: Web 코드의 redirectTo가 Supabase 허용 목록에 없습니다.

해결: Backend 서버의 .env에서 ADDITIONAL_REDIRECT_URLS 확인

ADDITIONAL_REDIRECT_URLS=https://example.com/*,https://www.example.com/*

수정 후 Auth 컨테이너 재생성:

cd /root/supabase/docker
docker compose up -d --force-recreate --no-deps auth

6.2 로그인 후 세션이 없음

원인:

  • Web 콜백에 code가 없음
  • PKCE 환경에서 exchangeCodeForSession() 미호출
  • 동일한 code를 두 번 교환함
  • 세션이 브라우저 저장소에 저장되지 않음

해결:

  1. 브라우저 개발자 도구 → 네트워크 탭에서 /auth/callback URL 확인
  2. URL에 code 파라미터가 있는지 확인
  3. exchangeCodeForSession() 호출 여부 확인
  4. 브라우저 저장소(localStorage 또는 sessionStorage)에 supabase-auth-token 확인

6.3 로그인 버튼을 눌러도 이동하지 않음

원인:

  • signInWithOAuth() 호출 오류
  • Supabase public URL 또는 anon key 오류
  • JavaScript 콘솔 오류

해결:

  1. 브라우저 개발자 도구 → 콘솔 탭에서 오류 확인
  2. Frontend .envPUBLIC_SUPABASE_URL 확인
  3. Supabase Client 생성 코드 확인

6.4 로그인 후 잘못된 사이트로 이동

원인: 다음 세 값의 불일치

Backend SITE_URL
Backend ADDITIONAL_REDIRECT_URLS
Frontend redirectTo

해결:

  1. Backend .envSITE_URLADDITIONAL_REDIRECT_URLS 확인
  2. Frontend 코드의 redirectTo 값 확인
  3. 모든 주소가 동일한 도메인으로 설정되었는지 확인

7. 보안 체크리스트

Google 로그인 설정이 완료되면 다음 보안 사항을 최종 점검합니다.

  • Google Client Secret은 Backend 서버 .env에만 저장되었습니다.
  • Backend .env가 Git 추적 대상이 아닌지 확인했습니다.
  • Google 승인 리디렉션 URI는 필요한 주소만 등록되었습니다.
  • ADDITIONAL_REDIRECT_URLS를 불필요하게 넓히지 않았습니다.
  • Frontend에는 anon/publishable key만 사용합니다.
  • SERVICE_ROLE_KEY는 브라우저와 공개 문서에 노출되지 않았습니다.
  • Web과 API 모두 HTTPS를 사용합니다.
  • 운영 환경에서 불필요한 localhost 복귀 주소를 제거했습니다.
  • 유출된 Client Secret이 있다면 즉시 폐기하고 새로 발급할 계획이 있습니다.

7.1 Secret 교체 절차

만약 Google Client Secret이 유출되었다면:

  1. Google Cloud에서 새 Client Secret을 발급합니다.
  2. Backend 서버 .envGOOGLE_SECRET을 교체합니다.
  3. Auth 컨테이너를 다시 생성합니다.
cd /root/supabase/docker
docker compose up -d --force-recreate --no-deps auth
  1. Google 로그인을 테스트합니다.
  2. 기존 Secret을 Google Cloud에서 삭제합니다.

8. 최종 설정 요약

8.1 Frontend .env

PUBLIC_SUPABASE_URL=https://sb.example.com
PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_OR_PUBLISHABLE_KEY

8.2 Frontend 코드: 로그인 시작

await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: `${window.location.origin}/auth/callback`,
  },
})

8.3 Frontend 코드: 세션 교환

const url = new URL(window.location.href)
const code = url.searchParams.get('code')

if (code) {
  await supabase.auth.exchangeCodeForSession(url.href)
}

8.4 가장 중요한 두 주소

Google이 돌아오는 Supabase API:
https://sb.example.com/auth/v1/callback

Supabase가 사용자를 보내는 Web:
https://example.com/auth/callback

9. 시리즈 완료

축하합니다! Google 소셜 로그인 설정을 완료했습니다.

시리즈 전체 요약:

1편: Web, API, Server 개념을 이해하고 Google Cloud Console에서 OAuth 클라이언트를 생성했습니다.

2편: Backend 서버의 .env에 Google Client 값을 설정하고 Auth 컨테이너를 재생성했습니다.

3편: Frontend에서 Supabase Client를 설정하고 Google 로그인을 연동한 후 처음부터 끝까지 테스트했습니다.

9.1 추가 참고 자료

문제가 발생했을 때는 다음 문서를 참조하세요.

9.2 공식 문서


작성일: 2026-07-18
적용 환경: Docker 기반 Self-hosted Supabase + SvelteKit

주파수 소통방 (0)

로딩 중...