Supabase + Next.js로 소셜 로그인(구글·카카오·페이스북) 완벽 구현하기

이 글은 Supabase를 백엔드로, Next.js 16 · React 19 · TypeScript · Tailwind CSS를 프론트엔드로 사용해 구글 · 카카오 · 페이스북 소셜 로그인을 처음부터 끝까지 구현하는 초보자용 완전 가이드입니다. 코드 한 줄, 콘솔 설정 한 단계까지 빠짐없이 따라 하면 누구나 똑같이 만들 수 있습니다.
이 예제는 화면이 딱 두 개입니다.
/login: 소셜 로그인 버튼 3개/: 로그인한 사람만 볼 수 있는 보호된 페이지(내 정보 + 로그아웃)

0. 먼저, OAuth 소셜 로그인의 원리부터

코드를 보기 전에 "소셜 로그인이 도대체 어떻게 동작하는지"를 알면 이후가 훨씬 쉽습니다.
한 줄 요약
OAuth는 우리 앱이 사용자의 비밀번호를 절대 받지 않고, 구글·카카오·페북 같은 믿을 수 있는 곳에 로그인을 대신 맡기는 방식입니다. 사용자는 구글에만 비밀번호를 입력하고, 구글은 우리에게 "이 사람 맞다"는 증표만 건네줍니다.
등장인물 4명
| 이름 | 역할 |
|---|---|
| 사용자(브라우저) | 로그인하는 사람 |
| 우리 앱(프론트) | 버튼을 보여주고 결과를 받음 (Next.js, localhost:3000) |
| 인증 서버 | 소셜 제공자와 비밀스럽게 대화하고 세션을 발급 (Supabase, 127.0.0.1:54321) |
| 소셜 제공자 | 실제로 사용자를 확인 (구글 / 카카오 / 페북) |
단계별 흐름
① [로그인 버튼 클릭] (브라우저, localhost:3000) supabase.auth.signInWithOAuth({ provider }) ↓ 브라우저를 인증 서버로 보냄 ② [Supabase authorize] (127.0.0.1:54321) ↓ 다시 소셜 제공자로 보냄 (client_id, scope, redirect_uri 포함) ③ [소셜 제공자 로그인·동의 화면] (accounts.google.com 등) 사용자가 여기서만 ID/비밀번호 입력 + "동의" ↓ "임시 코드(code)"를 들고 인증 서버로 되돌려보냄 ④ [Supabase callback] (127.0.0.1:54321/auth/v1/callback) ← 소셜 콘솔에 등록하는 주소 Supabase가 code + Client Secret 으로 소셜 제공자와 비밀 통신 → 진짜 사용자 정보를 받아 "세션"을 만든다 ↓ 우리 앱으로 돌려보냄 ⑤ [우리 앱 callback] (localhost:3000/auth/callback?code=...) code를 세션으로 교환하고 → 쿠키에 저장 ↓ ⑥ [홈] (localhost:3000/) 쿠키의 세션으로 "로그인한 사람"을 확인하고 내 정보 표시
왜 54321을 거쳐 3000으로 두 번 돌아올까?
- 소셜 제공자가 직접 대화하는 상대는 "인증 서버(Supabase, 54321)" 입니다. 비밀 통신(Client Secret으로 code↔token 교환)은 브라우저가 아니라 서버끼리 해야 안전하기 때문입니다.
- 그래서 소셜 콘솔에 등록하는 Redirect URI는 54321(Supabase) 이고, Supabase가 일을 끝낸 뒤 우리 프론트(3000) 로 최종 결과를 돌려줍니다.
- 즉 3000 = 우리 웹사이트, 54321 = 인증 백엔드. 둘은 다른 서버입니다.
이 그림이 머리에 있으면, 아래 설정들이 "왜 이렇게 하는지" 자연스럽게 이해됩니다.
1. 준비물
| 프로그램 | 확인 명령 | 비고 |
|---|---|---|
| Docker Desktop | docker ps | 로컬 Supabase가 Docker로 돌아갑니다 |
| Node.js 18+ | node -v | |
| Supabase CLI | supabase --version |
2. 프로젝트 만들기
Next.js 앱을 만들고, Supabase 라이브러리 두 개를 설치합니다.
# Next.js 앱 생성 (TypeScript + Tailwind + App Router + src 디렉터리) npx create-next-app@latest social-login-example \ --ts --tailwind --eslint --app --src-dir --import-alias "@/*" cd social-login-example # Supabase 클라이언트 라이브러리 설치 npm install @supabase/supabase-js @supabase/ssr
그리고 로컬 Supabase 백엔드를 초기화합니다.
supabase init
💡 이 글의 코드는 Next.js 16 / React 19 기준입니다. Next.js 16부터 미들웨어 파일 이름이
middleware.ts→proxy.ts로 바뀌었고,cookies()는await가 필요합니다. 아래 코드에 모두 반영돼 있습니다.
3. Supabase에 소셜 제공자 설정 (config.toml)
supabase/config.toml 을 열어 [auth] 부분을 다음처럼 맞추고, 세 개의 소셜 제공자 섹션을 추가합니다.
[auth] site_url = "http://localhost:3000" additional_redirect_urls = ["http://localhost:3000/**", "http://127.0.0.1:3000/**"] # ── 소셜 로그인 제공자 ────────────────────────────── # client_id / secret 은 각 개발자 콘솔에서 발급받아 supabase/.env 에 넣습니다. # (이 파일에는 비밀값을 직접 쓰지 않고 env(...) 로 참조합니다) [auth.external.google] enabled = true client_id = "env(SUPABASE_AUTH_GOOGLE_CLIENT_ID)" secret = "env(SUPABASE_AUTH_GOOGLE_SECRET)" redirect_uri = "http://127.0.0.1:54321/auth/v1/callback" # 로컬 개발에서 Google 로그인 시 nonce 검사를 건너뜁니다. skip_nonce_check = true [auth.external.facebook] enabled = true client_id = "env(SUPABASE_AUTH_FACEBOOK_CLIENT_ID)" secret = "env(SUPABASE_AUTH_FACEBOOK_SECRET)" # 페이스북은 http 주소를 localhost 만 허용(127.0.0.1 거부)하므로 localhost 사용 redirect_uri = "http://localhost:54321/auth/v1/callback" [auth.external.kakao] enabled = true client_id = "env(SUPABASE_AUTH_KAKAO_CLIENT_ID)" secret = "env(SUPABASE_AUTH_KAKAO_SECRET)" redirect_uri = "http://127.0.0.1:54321/auth/v1/callback"
⚠️ 꼭 알아둘 점: 로컬 Supabase는 provider마다
redirect_uri가 비어 있으면400 "missing redirect URI"로 거부합니다. 그래서 위처럼 명시적으로 콜백 주소를 넣어줘야 합니다. 이 주소는 잠시 후 각 소셜 콘솔에도 똑같이 등록합니다.
4. ⭐ 각 소셜 콘솔에서 키 발급 (직접 하셔야 하는 부분)
코드는 한 줄도 안 바뀝니다. 각 회사 개발자 사이트에서 Client ID / Secret 을 발급받아 supabase/.env 에 넣기만 하면 됩니다. 세 곳 모두 로그인 후 돌아올 주소(Redirect URI) 를 등록해야 하는데, 로컬 개발에서는 이 값입니다.
http://127.0.0.1:54321/auth/v1/callback
4-1. Google
구글은 ① 프로젝트 생성 → ② OAuth 동의 화면 → ③ OAuth 클라이언트 ID 발급 3단계입니다.

① 프로젝트 생성 — console.cloud.google.com에서 새 프로젝트를 만듭니다. 이름은 자유입니다. 소셜 로그인 키는 "프로젝트" 단위로 관리됩니다.

② 메뉴 이동 — 왼쪽 메뉴 API 및 서비스 → 사용자 인증 정보(Credentials) 로 들어갑니다.

③ 인증 정보 만들기 — 상단 + 사용자 인증 정보 만들기 → OAuth 클라이언트 ID 를 선택합니다.
④ OAuth 동의 화면(앱 정보) — 클라이언트를 만들기 전에 "동의 화면"부터 구성해야 합니다. 앱 이름(로그인할 때 사용자가 보게 될 이름)과 지원 이메일(본인 이메일)을 입력하고 다음으로 넘어갑니다.

⑤ 대상(Audience) → External — External(외부) 을 선택합니다. 앱이 "테스트 모드"로 시작하고, "테스트 사용자"에 추가한 구글 계정만 로그인됩니다.

⑥ 마무리 — "Google API 서비스: 사용자 데이터 정책"에 동의하고 만들기를 누르면 동의 화면 구성이 끝납니다.

⑦ 클라이언트 만들기 — 왼쪽 클라이언트(Clients) → + 클라이언트 만들기 를 누릅니다.

⑧ 가장 중요한 화면 —
- 애플리케이션 유형: 웹 애플리케이션
- 승인된 리디렉션 URI 에
http://127.0.0.1:54321/auth/v1/callback입력 (구글은 127.0.0.1을 http로 허용) - 만들기를 누르면 클라이언트 ID / 보안 비밀 이 나옵니다 →
supabase/.env에 넣습니다.
SUPABASE_AUTH_GOOGLE_CLIENT_ID=발급받은_클라이언트_ID SUPABASE_AUTH_GOOGLE_SECRET=발급받은_보안_비밀
4-2. Kakao

① 앱 생성 — developers.kakao.com → 내 애플리케이션에서 앱 이름·회사명·카테고리를 넣고 저장합니다.

② REST API 키 복사 — 왼쪽 앱 → 플랫폼 키 의 REST API 키 가 카카오의 "Client ID"입니다. 복사해서 supabase/.env 의 SUPABASE_AUTH_KAKAO_CLIENT_ID 에 넣습니다.

③ 비즈 앱(이메일용) — 왜 필요한가 — Supabase의 카카오 연동은 이메일(account_email) 을 항상 요청합니다. 그런데 카카오는 이메일 동의항목을 비즈 앱에서만 켤 수 있습니다. 사업자등록증이 없어도 개인 개발자는 본인인증으로 비즈 앱 전환이 가능합니다.

④ 개인 개발자 비즈 앱 전환 — "개인 개발자 비즈 앱 전환" 버튼을 누릅니다.

⑤ 전환 목적 선택 — 전환 목적을 "이메일 필수 동의" 로 고르고 저장하면 이메일 동의항목을 쓸 수 있게 됩니다.
📌 스크린샷에는 없지만 카카오에서 꼭 추가로 해야 하는 설정
- 카카오 로그인 활성화 — 제품 설정 → 카카오 로그인 → 활성화 ON
- Redirect URI 등록 — 카카오 로그인 → Redirect URI 에
http://127.0.0.1:54321/auth/v1/callback추가- 동의항목 켜기 — 카카오 로그인 → 동의항목에서 닉네임 · 프로필 사진 · 카카오계정(이메일) 을 켭니다. (안 켜면 로그인 시
KOE205에러)- Client Secret — 카카오 로그인 → 보안 → Client Secret 코드 생성 + 활성화(사용함) → 그 코드를
SUPABASE_AUTH_KAKAO_SECRET에 넣습니다. (안 맞으면 로그인 끝에서Unable to exchange external code에러)
SUPABASE_AUTH_KAKAO_CLIENT_ID=REST_API_키 SUPABASE_AUTH_KAKAO_SECRET=Client_Secret_코드
4-3. Facebook
① 앱 상세 정보 — developers.facebook.com → 앱 만들기. 앱 이름과 연락처 이메일(본인 이메일)을 입력합니다.

② 이용 사례 선택 — "Facebook 로그인을 통한 인증 및 사용자의 데이터 요청" 을 고릅니다.

③ 비즈니스 포트폴리오 — 개발/테스트용이라면 "아직 비즈니스 포트폴리오를 연결하고 싶지 않음" 으로 넘어가도 됩니다.

④ 게시 요구 사항(참고만) — 비즈니스 인증·앱 검수는 나중에 실서비스로 "게시"할 때 필요합니다. 지금은 건너뜁니다.
⑤ 개요 → 앱 만들기 — 입력 내용을 확인하고 마지막 "앱 만들기" 를 누르면 앱이 생성됩니다.

⑥ 권한 확인 — 이용 사례의 권한 및 기능에서 email, public_profile 이 "테스트 준비 완료"인지 확인합니다(기본 제공이라 검수 불필요).

⑦ 빠른 시작 → 웹(WWW) — Facebook 로그인 → 빠른 시작에서 플랫폼 웹(WWW) 을 선택합니다.

⑧ Redirect URI 입력 (가장 중요) — Facebook 로그인 → 설정 화면입니다. 위쪽 "URI 리디렉션 유효성 검사기"는 검사만 하는 도구이고, 실제 등록은 그 아래 "유효한 OAuth 리디렉션 URI" 칸에서 합니다.
- "클라이언트 OAuth 로그인" ON, "웹 OAuth 로그인" ON (이 토글이 꺼져 있으면 "차단된 URL" 에러)
- 칸에 콜백 주소 입력 → Enter(파란 태그) → 변경 내용 저장
- ⚠️ 페이스북은
127.0.0.1을 거부합니다. 로컬에서는http://localhost:54321/auth/v1/callback을 등록하세요. (localhost는 개발 모드에서 자동 허용)
⑨ 앱 역할(개발 모드 로그인 권한) — 개발 모드에서는 앱 역할(관리자/개발자/테스터)에 등록된 사람만 로그인됩니다. 앱을 만든 본인(관리자)은 바로 됩니다. 다른 사람으로 테스트하려면 앱 설정 → 앱 역할 → 역할 → 사람 추가 로 등록하세요.
앱 ID / 시크릿은 앱 설정 → 기본 설정에서 확인합니다.
SUPABASE_AUTH_FACEBOOK_CLIENT_ID=앱_ID SUPABASE_AUTH_FACEBOOK_SECRET=앱_시크릿
5. 환경 변수 파일 두 개
supabase/.env — 소셜 키 (백엔드 전용, 절대 공개 금지)
# 위 4단계에서 발급받은 값으로 채웁니다. SUPABASE_AUTH_GOOGLE_CLIENT_ID=여기에_구글_클라이언트_ID SUPABASE_AUTH_GOOGLE_SECRET=여기에_구글_시크릿 SUPABASE_AUTH_FACEBOOK_CLIENT_ID=여기에_페북_앱_ID SUPABASE_AUTH_FACEBOOK_SECRET=여기에_페북_시크릿 SUPABASE_AUTH_KAKAO_CLIENT_ID=여기에_카카오_REST_API_키 SUPABASE_AUTH_KAKAO_SECRET=여기에_카카오_Client_Secret
.env.local — Next.js가 쓰는 Supabase 접속 정보
supabase start 를 한 번 실행하면 출력되는 값(또는 supabase status)으로 채웁니다.
# NEXT_PUBLIC_ 로 시작하면 브라우저에도 전달됩니다 (공개돼도 안전한 값만!) NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 NEXT_PUBLIC_SUPABASE_ANON_KEY=여기에_supabase_status의_ANON_KEY
🔐 보안 핵심:
service_role(secret) 키는 절대 브라우저로 내보내지 마세요.NEXT_PUBLIC_접두사가 붙은 값만 브라우저로 나갑니다. 소셜Client Secret은 Supabase(서버)에만 두고,.env*파일은 반드시.gitignore로 막아 깃에 올리지 않습니다.
6. 소스 코드 (빌드 순서대로)
이제 코드를 작성합니다. 아래 순서 그대로 만들면 됩니다.
6-1. 브라우저용 Supabase 클라이언트 — src/lib/supabase/client.ts
버튼 클릭처럼 브라우저에서 실행되는 코드가 이걸 씁니다.
import { createBrowserClient } from "@supabase/ssr"; // 브라우저(클라이언트 컴포넌트)에서 사용하는 Supabase 클라이언트입니다. export function createClient() { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, ); }
6-2. 서버용 Supabase 클라이언트 — src/lib/supabase/server.ts
로그인 세션은 쿠키에 저장되므로, 쿠키를 읽고 쓸 수 있게 연결해 줍니다. Next.js 16에서 cookies() 는 await 가 필요합니다.
import { createServerClient } from "@supabase/ssr"; import { cookies } from "next/headers"; // 서버(서버 컴포넌트, 라우트 핸들러, 서버 액션)에서 사용하는 Supabase 클라이언트입니다. export async function createClient() { const cookieStore = await cookies(); return createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { getAll() { return cookieStore.getAll(); }, setAll(cookiesToSet) { try { cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options), ); } catch { // 서버 컴포넌트에서는 쿠키를 쓸 수 없어 에러가 납니다. // 세션 갱신은 proxy.ts 가 처리하므로 여기서는 무시해도 됩니다. } }, }, }, ); }
6-3. 세션 자동 갱신 — src/proxy.ts
모든 요청이 페이지에 도착하기 전에 여기를 거칩니다. 만료될 뻔한 로그인 토큰을 자동으로 갱신해 쿠키에 다시 저장합니다. (Next.js 16부터 미들웨어 이름이 proxy 로 바뀌었습니다.)
import { createServerClient } from "@supabase/ssr"; import { NextResponse, type NextRequest } from "next/server"; export async function proxy(request: NextRequest) { let response = NextResponse.next({ request }); const supabase = createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { getAll() { return request.cookies.getAll(); }, setAll(cookiesToSet) { cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value), ); response = NextResponse.next({ request }); cookiesToSet.forEach(({ name, value, options }) => response.cookies.set(name, value, options), ); }, }, }, ); // getUser() 를 호출해야 토큰이 갱신됩니다. (지우지 마세요!) await supabase.auth.getUser(); return response; } // 정적 파일(이미지 등)에는 실행하지 않습니다. export const config = { matcher: [ "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)", ], };
6-4. 로그인 화면 — src/app/login/page.tsx
'use client' 가 맨 위에 있으면 이 컴포넌트는 브라우저에서 실행됩니다. 버튼을 누르면 signInWithOAuth 가 소셜 로그인 페이지로 보냅니다.
"use client"; import { createClient } from "@/lib/supabase/client"; // 지원할 소셜 로그인 목록 (버튼 추가/삭제가 쉽도록 배열로) const providers = [ { id: "google", label: "Google로 시작하기", color: "bg-red-500 hover:bg-red-600 text-white" }, { id: "kakao", label: "카카오로 시작하기", color: "bg-yellow-400 hover:bg-yellow-500 text-black" }, { id: "facebook", label: "Facebook으로 시작하기", color: "bg-blue-600 hover:bg-blue-700 text-white" }, ] as const; export default function LoginPage() { const supabase = createClient(); // 소셜 로그인 버튼을 누르면 실행됩니다. async function signIn(provider: "google" | "kakao" | "facebook") { await supabase.auth.signInWithOAuth({ provider, options: { // 로그인 성공 후 소셜 제공자가 우리 사이트의 이 주소로 다시 보내줍니다. redirectTo: `${window.location.origin}/auth/callback`, }, }); } return ( <main className="flex min-h-screen flex-col items-center justify-center gap-8 bg-gray-50 p-6"> <div className="w-full max-w-sm space-y-6 rounded-2xl bg-white p-8 shadow-lg"> <div className="text-center"> <h1 className="text-2xl font-bold text-gray-900">소셜 로그인 예제</h1> <p className="mt-2 text-sm text-gray-500"> 아래 버튼으로 간편하게 로그인하세요 </p> </div> <div className="space-y-3"> {providers.map((p) => ( <button key={p.id} onClick={() => signIn(p.id)} className={`w-full rounded-lg px-4 py-3 font-medium transition ${p.color}`} > {p.label} </button> ))} </div> </div> </main> ); }
6-5. 콜백 라우트 — src/app/auth/callback/route.ts
소셜 로그인에 성공하면 사용자는 이 주소로 돌아옵니다. 주소에 붙어온 임시 코드(code)를 "로그인 세션"으로 교환합니다.
import { NextResponse } from "next/server"; import { createClient } from "@/lib/supabase/server"; export async function GET(request: Request) { const { searchParams, origin } = new URL(request.url); const code = searchParams.get("code"); const next = searchParams.get("next") ?? "/"; // 로그인 후 이동할 페이지 if (code) { const supabase = await createClient(); const { error } = await supabase.auth.exchangeCodeForSession(code); if (!error) { // 세션 쿠키 저장 완료 → 원하는 페이지로 return NextResponse.redirect(`${origin}${next}`); } } // 코드가 없거나 교환 실패 시 로그인 페이지로 return NextResponse.redirect(`${origin}/login?error=auth`); }
6-6. 로그아웃 (서버 액션) — src/app/actions.ts
'use server' 가 맨 위에 있으면 이 함수는 서버에서 실행됩니다.
"use server"; import { redirect } from "next/navigation"; import { createClient } from "@/lib/supabase/server"; export async function logout() { const supabase = await createClient(); await supabase.auth.signOut(); // 세션 쿠키를 지웁니다. redirect("/login"); }
6-7. 보호된 홈 — src/app/page.tsx
서버에서 실행되는 페이지입니다. 로그인하지 않았으면 /login 으로 보내고, 로그인했으면 내 정보를 보여줍니다.
import { redirect } from "next/navigation"; import { createClient } from "@/lib/supabase/server"; import { logout } from "./actions"; export default async function Home() { const supabase = await createClient(); // 현재 로그인한 사용자 정보를 가져옵니다. const { data: { user }, } = await supabase.auth.getUser(); // 로그인하지 않았으면 로그인 페이지로 보냅니다. if (!user) { redirect("/login"); } // 이번 로그인에 사용한 provider = 가장 최근에 로그인한 identity. // (Supabase 는 같은 이메일을 한 계정으로 합치므로, "이번에 누른 버튼"은 // identity 들의 last_sign_in_at 중 가장 최신 값으로 알아냅니다.) const identities = user.identities ?? []; const currentProvider = [...identities].sort((a, b) => (b.last_sign_in_at ?? "").localeCompare(a.last_sign_in_at ?? ""), )[0]?.provider ?? user.app_metadata.provider; return ( <main className="flex min-h-screen flex-col items-center justify-center gap-6 bg-gray-50 p-6"> <div className="w-full max-w-md space-y-6 rounded-2xl bg-white p-8 shadow-lg"> <h1 className="text-2xl font-bold text-gray-900">로그인 성공! 🎉</h1> <dl className="space-y-2 text-sm"> <div className="flex justify-between border-b pb-2"> <dt className="text-gray-500">이메일</dt> <dd className="font-medium text-gray-900">{user.email ?? "(없음)"}</dd> </div> <div className="flex justify-between border-b pb-2"> <dt className="text-gray-500">로그인 방법</dt> <dd className="font-medium text-gray-900">{currentProvider}</dd> </div> <div className="flex justify-between"> <dt className="text-gray-500">사용자 ID</dt> <dd className="font-mono text-xs text-gray-700">{user.id}</dd> </div> </dl> {/* form 의 action 으로 서버 함수(logout)를 직접 연결할 수 있습니다 */} <form action={logout}> <button type="submit" className="w-full rounded-lg bg-gray-900 px-4 py-3 font-medium text-white transition hover:bg-gray-700" > 로그아웃 </button> </form> </div> </main> ); }
7. 실행하기
supabase start # 로컬 백엔드 켜기 (Docker, 처음엔 이미지 다운로드로 몇 분) npm run dev # http://localhost:3000
브라우저에서 http://localhost:3000 에 접속 → 자동으로 /login 으로 이동 → 소셜 버튼을 누르면 끝까지 동작합니다. 🎉
8. 한 단계 더: 로컬 Supabase vs 클라우드 Supabase
지금까지는 로컬 모드(supabase start 로 내 PC에서 백엔드 실행)였습니다. 친구들과 공유하거나 배포할 땐 클라우드 모드(supabase.com 프로젝트)가 편합니다. 프론트엔드는 둘 다 localhost:3000 에서 똑같이 돕니다 — 바뀌는 건 "프론트가 어떤 Supabase에 접속하느냐" 뿐입니다.
| 🖥️ 로컬 모드 | ☁️ 클라우드 모드 | |
|---|---|---|
| Supabase 위치 | 내 PC (Docker) 127.0.0.1:54321 | supabase.com <프로젝트>.supabase.co |
| 실행 전 필요 | supabase start (Docker) | 없음 (항상 떠 있음) |
| 소셜 키 설정 | config.toml + supabase/.env | 대시보드 → Authentication → Providers |
| Redirect URI | http://127.0.0.1:54321/auth/v1/callback | https://<프로젝트>.supabase.co/auth/v1/callback |
.env.local | 로컬 URL + 로컬 anon 키 | 클라우드 URL + 클라우드 anon 키 |
클라우드로 전환할 때 할 일
- supabase.com에서 프로젝트를 만들고 Authentication → Providers 에서 각 provider를 켜고 Client ID/Secret 입력 (또는
supabase link후supabase config push). .env.local의 URL/anon 키를 클라우드 값으로 변경.- 각 소셜 콘솔의 Redirect URI 에 클라우드 콜백(
https://<프로젝트>.supabase.co/auth/v1/callback)을 추가.
클라우드 모드면
supabase start가 필요 없어, 사양이 낮은 PC에서도 Docker 없이npm run dev만으로 동작합니다.
9. 알아두면 좋은 동작들
같은 이메일 = 한 계정 (Identity Linking)
Supabase는 같은 (인증된) 이메일로 여러 provider에 로그인하면 자동으로 한 계정에 연결합니다. 예를 들어 같은 이메일로 구글·페북에 로그인하면, 어느 버튼으로 들어와도 같은 계정·같은 데이터를 보게 됩니다(좋은 UX). 이 동작은 보안상 의도된 것이라 끌 수 없습니다. "이번에 어떤 버튼으로 들어왔는지"는 위 page.tsx 처럼 identities 의 last_sign_in_at 으로 알아낼 수 있습니다.
자주 만나는 에러
KOE205(카카오) : 동의항목(닉네임/프로필/이메일)이 콘솔에 설정되지 않음 → 동의항목을 켜세요.redirect_uri_mismatch(구글) : 소셜 콘솔의 승인된 리디렉션 URI 와 Supabase가 보낸 주소가 다름 → 콘솔에 정확히 등록(끝 슬래시·http/https 주의).- "차단된 URL" (페북) : "웹 OAuth 로그인" 토글이 꺼져 있거나, 콜백이 목록에 저장 안 됨 → 토글 ON + 저장.
Unable to exchange external code: Client Secret 값이 콘솔과 다름 → 정확한 시크릿으로 교체.
10. 배포(프로덕션) 시 주의점
service_role(secret) 키는 절대 클라이언트에 노출하지 않습니다.NEXT_PUBLIC_변수는 브라우저로 나가므로 anon/publishable 키만 둡니다.- 소셜 Client Secret 은 서버에만 두고,
.env*는.gitignore로 막아 깃에 올리지 않습니다. - DB 테이블을 만들면 RLS(Row Level Security) 를 반드시 켭니다.
- 서버에서 사용자 신원을 확인할 땐
getUser()(서버 검증)를 쓰고,getSession()값만 믿고 권한을 판단하지 않습니다. - 프로덕션은 HTTPS 를 사용하고, 토큰 갱신은
proxy.ts가 처리하도록 둡니다.
마치며
Supabase 덕분에 복잡한 OAuth 토큰 교환·세션 관리를 직접 구현하지 않고, 버튼 + 콜백 라우트 + 보호된 페이지만으로 구글·카카오·페이스북 로그인을 완성했습니다. 핵심은 단 두 가지입니다.
- 흐름 이해 — 버튼 → Supabase(54321) → 소셜 제공자 → Supabase 콜백 → 우리 앱(3000)
- 콘솔 설정 — 각 provider에서 키 발급 + Redirect URI 등록
이 두 가지만 잡으면 어떤 소셜 로그인도 똑같이 붙일 수 있습니다. 즐거운 코딩 되세요! 🚀



댓글
댓글을 작성하려면 이 필요합니다.