2026 카카오 로그인 Next.js 연동 가이드 (Supabase Auth)
한국 SaaS에서 가장 높은 전환율을 보이는 로그인 방식은 단연 카카오 로그인입니다. 카카오톡 MAU 4,800만 시대에, 이메일/비밀번호 대신 카카오 버튼 하나로 가입 허들을 낮출 수 있습니다.
이 글에서는 Next.js App Router + Supabase Auth를 활용해서 카카오 앱 등록 → OAuth 설정 → 로그인 구현 → 콜백 처리 → 미들웨어 인증 보호까지 실제 프로덕션 코드로 보여드립니다.
Supabase Auth를 쓰면 JWT 관리, 세션 갱신, PKCE 플로우를 직접 구현할 필요가 없습니다. 카카오 디벨로퍼스에서 앱을 등록하고, Supabase 대시보드에서 Client ID/Secret만 넣으면 끝입니다.
1단계: 카카오 디벨로퍼스에서 앱 등록
먼저 카카오 디벨로퍼스에 접속해서 애플리케이션을 등록합니다.
- 내 애플리케이션 → 애플리케이션 추가하기를 클릭
- 앱 이름, 사업자 정보를 입력하고 저장
- 앱 키 탭에서
REST API 키를 복사 (이것이 Client ID) - 보안 탭에서
Client Secret코드를 생성하고 복사
그 다음, 카카오 로그인 설정을 합니다:
- 카카오 로그인 → 활성화 설정을 ON으로 변경
- Redirect URI에 Supabase 콜백 URL을 등록:
https://<your-project-ref>.supabase.co/auth/v1/callbackRedirect URI를 정확히 입력하세요. 끝에 슬래시(/)가 있으면 안 됩니다. Supabase 대시보드 → Authentication → Providers → Kakao에서 정확한 URL을 확인할 수 있습니다.
마지막으로 동의항목을 설정합니다:
- 닉네임 — 필수 동의 (프로필 표시용)
- 카카오계정(이메일) — 선택 동의 (로그인 식별용, 비즈앱 전환 시 필수 가능)
- 프로필 이미지 — 선택 동의 (아바타 표시용)
비즈 앱 전환을 권장합니다. 개인 앱은 이메일 동의항목을 "선택"으로만 설정할 수 있어서, 사용자가 이메일 제공을 거부할 수 있습니다. 비즈 앱으로 전환하면 이메일을 "필수"로 받을 수 있어 사용자 식별이 확실해집니다.
2단계: Supabase에서 카카오 OAuth 설정
Supabase 대시보드에서 카카오 프로바이더를 활성화합니다:
- Authentication → Providers → Kakao로 이동
- Enable Kakao provider를 ON
- Client ID에 카카오 REST API 키 입력
- Client Secret에 카카오 Client Secret 입력
- 저장
그리고 .env.local에 Supabase 환경변수를 설정합니다:
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://<your-project-ref>.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...your-anon-key이 두 값은 Supabase 대시보드 → Settings → API에서 확인할 수 있습니다. NEXT_PUBLIC_ 접두사가 붙어야 브라우저에서 접근 가능합니다.
3단계: Supabase 클라이언트 만들기
먼저 브라우저에서 사용할 Supabase 클라이언트를 만듭니다. @supabase/ssr 패키지의 createBrowserClient를 사용합니다:
import { createBrowserClient } from "@supabase/ssr";
export function createSupabaseBrowserClient() {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
if (!url || !key) {
throw new Error(
"NEXT_PUBLIC_SUPABASE_URL과 NEXT_PUBLIC_SUPABASE_ANON_KEY를 " +
".env.local에 설정하세요."
);
}
return createBrowserClient(url, key);
}핵심 포인트:
- 환경변수 검증 — 클라이언트 생성 전에 반드시 환경변수가 있는지 확인합니다. 없으면 명확한 에러 메시지로 빠르게 디버깅할 수 있습니다.
- @supabase/ssr — Next.js App Router에서는
@supabase/supabase-js의createClient대신 SSR 전용 패키지를 사용해야 쿠키 기반 세션이 올바르게 동작합니다.
4단계: signInWithOAuth로 카카오 로그인 구현
이제 실제 로그인 페이지를 만듭니다. Supabase의 signInWithOAuth 하나로 카카오 로그인이 완성됩니다:
"use client";
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { createSupabaseBrowserClient } from "@/lib/supabase/client";
export default function LoginPage() {
const router = useRouter();
const [error, setError] = useState("");
const supabase = createSupabaseBrowserClient();
const handleOAuthLogin = async (
provider: "kakao" | "google"
) => {
setError("");
const { error: authError } =
await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo:
window.location.origin + "/auth/callback",
},
});
if (authError) {
setError(authError.message);
}
};
return (
<div className="flex min-h-screen items-center justify-center">
<div className="w-full max-w-sm">
<button
type="button"
onClick={() => handleOAuthLogin("kakao")}
className="w-full rounded-lg bg-[#FEE500] px-4 py-2.5
text-sm font-medium text-[#191919]"
>
카카오로 로그인
</button>
<button
type="button"
onClick={() => handleOAuthLogin("google")}
className="mt-3 w-full rounded-lg border
border-gray-300 bg-white px-4 py-2.5
text-sm font-medium text-gray-700"
>
구글로 로그인
</button>
{error && (
<p className="mt-4 text-sm text-red-600">
{error}
</p>
)}
</div>
</div>
);
}코드에서 주목할 부분:
- signInWithOAuth — provider에
"kakao"를 넘기면 Supabase가 자동으로 카카오 OAuth 플로우를 시작합니다. 카카오 로그인 팝업이 뜨고, 사용자가 동의하면redirectTo로 돌아옵니다. - redirectTo —
window.location.origin + "/auth/callback"을 지정합니다. 이 URL에서 인증 코드를 세션으로 교환합니다. - 카카오 노란색 — 카카오 브랜드 가이드라인에 따라 배경색
#FEE500, 글자색#191919를 사용합니다.
Google 로그인도 같은 코드입니다. provider만 "google"로 바꾸면 됩니다. Supabase Auth의 장점은 프로바이더 추가가 코드 변경 없이 대시보드 설정만으로 가능하다는 점입니다. Apple, GitHub, Naver도 같은 패턴입니다.
5단계: Auth Callback 라우트 처리
카카오 로그인 후 Supabase가 /auth/callback으로 리다이렉트합니다. 여기서 인증 코드를 세션으로 교환해야 합니다:
import { NextResponse } from "next/server";
import { createSupabaseServerClient } 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") ?? "/dashboard";
// Open Redirect 방지
const safePath =
next.startsWith("/") && !next.startsWith("//")
? next
: "/dashboard";
if (code) {
const supabase = await createSupabaseServerClient();
const { error } =
await supabase.auth.exchangeCodeForSession(code);
if (!error) {
return NextResponse.redirect(
`${origin}${safePath}`
);
}
}
// 인증 실패 시 로그인 페이지로
return NextResponse.redirect(`${origin}/login`);
}이 라우트가 하는 일:
- URL에서
code파라미터를 추출합니다. Supabase가 카카오에서 받은 인증 코드를 여기에 담아줍니다. exchangeCodeForSession으로 코드를 JWT 세션으로 교환합니다. 이 과정에서 Supabase가 카카오 Access Token을 받아오고,auth.users테이블에 사용자를 생성하거나 업데이트합니다.- 성공하면
/dashboard로, 실패하면/login으로 리다이렉트합니다.
Open Redirect 취약점에 주의하세요.
next 파라미터를 그대로 리다이렉트에 사용하면, 공격자가 ?next=https://evil.com으로 사용자를 외부 사이트로 보낼 수 있습니다. 반드시 /로 시작하고 //로 시작하지 않는지 검증하세요.
6단계: 미들웨어로 인증 보호
로그인 후 보호가 필요한 페이지(대시보드, 설정 등)에 미인증 사용자가 접근하지 못하도록 미들웨어에서 처리합니다:
import { createServerClient } from "@supabase/ssr";
import {
NextResponse,
type NextRequest,
} from "next/server";
/** 인증 불필요한 공개 라우트 */
const PUBLIC_ROUTES = [
"/",
"/login",
"/signup",
"/auth",
"/api",
"/terms",
"/privacy",
"/refund",
"/pricing",
"/thank-you",
];
function isPublicRoute(pathname: string): boolean {
return PUBLIC_ROUTES.some(
(route) =>
pathname === route ||
pathname.startsWith(`${route}/`)
) ||
pathname === "/robots.txt" ||
pathname === "/sitemap.xml";
}
export async function updateSession(
request: NextRequest
) {
const supabaseUrl =
process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey =
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
// 환경변수 미설정 시 스킵 (초기 배포/데모)
if (!supabaseUrl || !supabaseAnonKey) {
return NextResponse.next({ request });
}
let supabaseResponse = NextResponse.next({ request });
const supabase = createServerClient(
supabaseUrl,
supabaseAnonKey,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) =>
request.cookies.set(name, value)
);
supabaseResponse = NextResponse.next({
request,
});
cookiesToSet.forEach(
({ name, value, options }) =>
supabaseResponse.cookies.set(
name,
value,
options
)
);
},
},
}
);
const {
data: { user },
} = await supabase.auth.getUser();
const pathname = request.nextUrl.pathname;
// 미인증 → 로그인 페이지로 리다이렉트
if (!isPublicRoute(pathname) && !user) {
const url = request.nextUrl.clone();
url.pathname = "/login";
url.searchParams.set("redirectTo", pathname);
return NextResponse.redirect(url);
}
// 이미 로그인한 사용자 → 대시보드로
if (
user &&
(pathname === "/login" || pathname === "/signup")
) {
const url = request.nextUrl.clone();
url.pathname = "/dashboard";
return NextResponse.redirect(url);
}
return supabaseResponse;
}미들웨어의 핵심 로직:
- PUBLIC_ROUTES 화이트리스트 — 인증 없이 접근 가능한 경로를 명시적으로 정의합니다. 새 공개 페이지를 추가할 때마다 여기에 등록하세요.
- 쿠키 기반 세션 — Supabase SSR 패키지가 요청/응답의 쿠키를 자동으로 관리합니다. 미들웨어에서
getUser()를 호출하면 쿠키에서 세션을 읽어 사용자 인증 여부를 확인합니다. - 리다이렉트 체인 — 미인증 사용자는
/login?redirectTo=/dashboard처럼 원래 가려던 경로를 쿼리 파라미터로 보존합니다. 로그인 성공 후 원래 페이지로 돌아갈 수 있습니다. - 환경변수 미설정 시 스킵 — Supabase를 아직 연결하지 않은 초기 배포 상태에서도 앱이 정상 동작합니다.
이 미들웨어를 src/middleware.ts에서 호출합니다:
import { type NextRequest } from "next/server";
import { updateSession } from "@/lib/supabase/middleware";
export async function middleware(request: NextRequest) {
return await updateSession(request);
}
export const config = {
matcher: [
// 정적 파일과 _next 제외
"/((?!_next/static|_next/image|favicon.ico|.*\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};프로덕션 체크리스트
개발 환경에서 잘 되는데 프로덕션에서 안 되는 경우가 많습니다. 배포 전에 반드시 확인하세요:
- 카카오 Redirect URI: 프로덕션 도메인의 Supabase 콜백 URL이 카카오 디벨로퍼스에 등록되어 있는지 확인. 개발용 localhost와 프로덕션 URL 두 개 다 등록하세요.
- 비즈 앱 전환: 개인 앱은 하루 카카오 로그인 요청 제한이 있습니다. 서비스 출시 전에 비즈 앱으로 전환하세요.
- 이메일 필수 동의: 비즈 앱 전환 후 카카오계정(이메일) 동의항목을 "필수"로 변경. 이메일 없이 가입된 사용자를 처리하는 로직이 없다면 반드시 필수로 설정하세요.
- Supabase Site URL: Supabase 대시보드 → Authentication → URL Configuration에서 Site URL을 프로덕션 도메인으로 설정. 이걸 빠뜨리면 콜백 후 localhost로 리다이렉트됩니다.
- Redirect URLs 화이트리스트: 같은 설정 페이지에서 Redirect URLs에 프로덕션 도메인
https://yourdomain.com/**을 추가하세요. - 에러 모니터링: 카카오 로그인 실패를 Sentry 등으로 모니터링. 토큰 만료, 네트워크 오류, 동의 철회 등 다양한 실패 케이스가 있습니다.
- 로그아웃 시 카카오 연결 해제: 사용자가 서비스를 탈퇴하면 카카오 연결 해제 API도 호출해야 합니다. 안 하면 사용자의 카카오 설정에 앱이 계속 남아있습니다.
전체 파일 구조 정리
src/
├── lib/
│ └── supabase/
│ ├── client.ts # 브라우저용 Supabase 클라이언트
│ ├── server.ts # 서버용 Supabase 클라이언트
│ └── middleware.ts # 인증 미들웨어 로직
├── app/
│ ├── (auth)/
│ │ └── login/
│ │ └── page.tsx # 카카오/구글 로그인 페이지
│ ├── auth/
│ │ └── callback/
│ │ └── route.ts # OAuth 콜백 처리
│ └── middleware.ts # Next.js 미들웨어 진입점
└── .env.local # Supabase 환경변수마무리
여기까지 따라오셨다면 카카오 로그인 + Supabase Auth + Next.js App Router로 인증 시스템의 전체 흐름을 이해하셨을 겁니다. 핵심을 정리하면:
- 카카오 디벨로퍼스에서 앱을 등록하고, Redirect URI를 정확히 설정할 것
signInWithOAuth한 줄이면 카카오 로그인이 완성되지만, 콜백 라우트와 미들웨어가 반드시 있어야 세션이 유지됨- 프로덕션 배포 전에 비즈 앱 전환, Site URL 설정, 이메일 필수 동의를 꼭 확인할 것
하지만 인증은 SaaS의 시작일 뿐입니다. 실제로는 결제 연동(토스페이먼츠), DB 설계, API 라우팅, 관리자 대시보드, 이메일 발송, 알림톡 등등 해야 할 것들이 산더미입니다.
이 글에서 보여드린 코드는 실제로 한국형 SaaS 보일러플레이트를 만들면서 작성한 코드입니다. 카카오 로그인뿐 아니라, 토스페이먼츠 결제, Supabase, tRPC, Drizzle ORM, 알림톡까지 모두 포함된 Next.js 스타터킷이 있습니다.