Supabase Auth 실전 가이드: 이메일 인증, OAuth, 세션 관리

Supabase Dashboard를 열고 Authentication 메뉴에 들어갔을 때 잠시 막막했습니다. 이메일 인증, Magic Link, OAuth, SSR 설정까지 선택지가 너무 많았습니다. 어떤 방식을 써야 하고, 어떻게 설정해야 할까요?
비슷한 고민을 하고 있다면 걱정하지 마세요. 예전에 작은 프로젝트에 로그인 기능을 추가하면서 반나절 넘게 씨름한 끝에, 각 인증 방식마다 적합한 상황이 따로 있다는 사실을 알게 됐습니다. 이 글에서는 Supabase Auth의 핵심 세 가지를 하나로 연결해 설명합니다. 이메일 인증, OAuth 연동, 그리고 많은 개발자가 어려워하는 세션 관리입니다. 다 읽고 나면 약 30분 안에 완전한 사용자 인증 시스템을 구축할 수 있을 것입니다.
이메일 인증 — 가장 기본적인 인증 방식
솔직히 이메일 인증은 Supabase Auth에서 가장 간단하지만 가장 쉽게 간과되는 부분이기도 합니다.
Dashboard에서 Authentication → Providers → Email로 이동하면 “Confirm Email”이라는 스위치가 있습니다. 이 스위치는 사용자가 가입한 뒤 이메일을 인증해야 로그인할 수 있는지를 결정합니다. Hosted 프로젝트에서는 기본적으로 활성화되어 있습니다. 즉, 사용자는 가입 후 인증 메일을 받고 링크를 눌러야 계정을 정식으로 활성화할 수 있습니다.
저는 처음 설정할 때 실수로 이 옵션을 꺼 버렸습니다. 그랬더니 아무 이메일이나 입력해도 로그인할 수 있어서 스팸 계정이 잔뜩 생겼습니다. 나중에야 프로덕션 환경에서는 이 스위치를 반드시 켜야 한다는 사실을 알았습니다.
설정 코드는 아주 간단합니다.
// 등록 시 이메일 인증 실행
const { data, error } = await supabase.auth.signUp({
email: '[email protected]',
password: 'secure-password',
options: {
emailRedirectTo: 'https://yourapp.com/auth/callback'
}
})
여기서 주의할 부분은 emailRedirectTo 매개변수입니다. 사용자가 메일의 인증 링크를 누르면 이 주소로 이동합니다. 애플리케이션 홈 화면이나 별도의 환영 페이지로 지정할 수 있습니다.
메일 템플릿의 경우 Supabase에는 이메일 확인, 비밀번호 재설정, Magic Link 메일 등 여러 템플릿이 내장되어 있습니다. Dashboard의 Email Templates에서 바로 편집할 수 있습니다. Resend나 SendGrid 같은 자체 SMTP 서비스를 사용하려면 Auth Hooks에서 설정할 수 있습니다. 다만 이는 고급 활용에 해당하므로 처음 시작할 때는 기본 템플릿만으로도 충분합니다.
직접 겪었던 또 다른 문제는 로컬 개발 환경에서 이메일 인증이 막힐 수 있다는 점입니다. Supabase 로컬 인스턴스의 메일 서비스는 기본적으로 실제 메일을 보내지 않기 때문입니다. Mailcatcher 같은 도구로 테스트 메일을 확인하거나, 로컬 개발 중에는 Confirm Email을 잠시 껐다가 정식 출시 전에 다시 켜면 됩니다.
OAuth 연동 — 더 편리한 원클릭 로그인
OAuth 로그인은 사용자 경험 측면에서 정말 유용합니다. 사용자는 비밀번호를 기억할 필요 없이 GitHub나 Google 버튼을 한 번 누르면 로그인할 수 있으며, 보통 이메일 가입보다 전환율도 훨씬 높습니다.
Supabase가 지원하는 OAuth Provider는 GitHub, Google, Facebook, Apple, Azure, Twitter, Discord 등 15개가 넘습니다. 제가 가장 자주 사용하는 것은 GitHub와 Google이며, 두 서비스의 설정 절차가 가장 명확합니다.
먼저 GitHub OAuth를 살펴보겠습니다. GitHub에서 OAuth App을 만들어야 합니다(Settings → Developer settings → OAuth Apps → New OAuth App). 핵심은 Callback URL을 정확히 입력하는 것입니다.
https://<프로젝트 ref>.supabase.co/auth/v1/callback
로컬 개발 환경에서는 다음 주소를 사용합니다.
http://localhost:54321/auth/v1/callback
그런 다음 GitHub OAuth App의 Client ID와 Client Secret을 Supabase Dashboard(Authentication → Providers → GitHub)에 복사합니다. 저장한 뒤 클라이언트에서 호출하는 코드는 아주 간단합니다.
// GitHub OAuth 로그인
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: 'https://yourapp.com/auth/callback'
}
})
Google OAuth도 절차는 비슷하지만 한 가지 차이가 있습니다. Google에서는 Web, iOS, Android 세 플랫폼의 Client ID를 구분합니다. 애플리케이션이 Web과 모바일을 모두 지원한다면 각각 설정해야 합니다.
참고로 OAuth 로그인 후 Supabase는 provider token을 제공합니다. 이 token으로 서드파티 API를 호출할 수 있습니다. 예를 들어 GitHub token으로 사용자의 저장소 목록을 가져오거나 Google token으로 Google Drive에 접근할 수 있습니다. 서드파티 서비스를 연동해야 하는 애플리케이션에서 특히 유용한 기능입니다.
다만 OAuth에도 주의할 점이 있습니다. 로컬 개발 환경에서 callback URL을 잘못 설정하는 것은 매우 흔한 실수입니다. 처음 설정했을 때 프런트엔드 포트인 3000을 입력했더니 로그인 후 페이지에서 바로 오류가 발생했습니다. callback은 프런트엔드 포트가 아니라 Supabase 포트를 가리켜야 합니다.
세션 관리 — JWT와 PKCE 흐름
이 부분이 가장 이해하기 어려울 수 있습니다. 솔직히 저도 처음에는 JWT, refresh token, PKCE가 어떤 방식으로 서로 연결되어 작동하는지 완전히 이해하지 못했습니다.
Supabase 세션은 두 부분으로 구성됩니다. 하나는 access token(단기 JWT)이고, 다른 하나는 refresh token(장기 token)입니다. access token의 기본 유효 기간은 1시간입니다. 공식 권장 사항은 시계 오차를 고려해 최소 5분보다 짧게 설정하지 않는 것입니다. refresh token은 일회용이며 새로운 access token을 발급받는 데 사용됩니다.
여기서 중요한 세부 사항이 하나 있습니다. refresh token에는 10초의 재사용 허용 시간이 있습니다. 이것은 무엇을 의미할까요? SSR 환경에서 여러 요청이 동시에 token 갱신을 시도하더라도 Supabase가 10초 이내의 중복 갱신을 허용해 세션이 예기치 않게 종료되지 않도록 한다는 뜻입니다. 프런트엔드와 백엔드가 동시에 session을 조작하는 상황은 흔하므로 합리적인 설계입니다.
다음은 PKCE입니다. Next.js나 다른 SSR 프레임워크를 사용한다면 PKCE 흐름을 반드시 설정해야 합니다. 그 이유는 암시적 흐름(Implicit flow)이 token을 URL에 직접 노출해 SSR 환경에서 안전하지 않기 때문입니다. PKCE는 code verifier를 이용해 token 교환 과정을 보호합니다.
PKCE를 설정하려면 클라이언트를 초기화할 때 두 매개변수를 추가해야 합니다.
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
auth: {
detectSessionInUrl: true,
flowType: 'pkce'
}
})
그런 다음 code 교환을 처리할 callback route가 필요합니다.
// Next.js App Router - app/auth/callback/route.ts
import { NextResponse } from 'next/server'
import { createClient } from '@/utils/supabase/server'
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url)
const code = searchParams.get('code')
if (code) {
const supabase = await createClient()
const { error } = await supabase.auth.exchangeCodeForSession(code)
if (!error) {
return NextResponse.redirect(`${origin}/dashboard`)
}
}
return NextResponse.redirect(`${origin}/auth/error`)
}
auth code의 유효 기간은 5분이며 한 번만 교환할 수 있습니다. 디버깅 중 code 교환에 실패한다면 대부분 시간이 초과됐거나 동일한 code를 다시 사용한 경우입니다.
Supabase는 세 가지 세션 제한 모드도 지원합니다. 고정 시간이 지나면 강제로 만료되는 시간 제한(Time-boxed), 사용자가 오랫동안 활동하지 않으면 만료되는 비활성 시간 제한(Inactivity timeout), 계정 하나에 활성 세션 하나만 허용하는 사용자별 단일 세션(Single session per user)입니다. 이런 모드는 SOC 2나 HIPAA 규정 준수가 필요한 애플리케이션에서 특히 유용합니다.
실전 조언과 자주 묻는 질문
여기까지 읽었다면 어떤 인증 방식을 선택해야 하는지 궁금할 것입니다.
간단히 정리하면 이메일 인증은 사용자의 완전한 정보가 필요한 정식 가입 절차에 적합합니다. OAuth는 개발자 도구나 B2B 애플리케이션처럼 빠른 로그인이 중요한 환경에 적합합니다. Magic Link는 임시 접근이나 모바일 우선 애플리케이션처럼 비밀번호가 필요 없는 환경에 적합합니다.
세 가지 방식을 비교하면 다음과 같습니다.
| 방식 | 적합한 환경 | 장점 | 단점 |
|---|---|---|---|
| 이메일 인증 | 정식 가입 절차 | 정보가 완전하고 제어하기 쉬움 | 사용자가 비밀번호를 기억해야 함 |
| OAuth | 빠른 로그인 | 비밀번호가 필요 없고 전환율이 높음 | 서드파티 서비스의 안정성에 의존함 |
| Magic Link | 비밀번호 없는 환경 | 안전하고 간단함 | 로그인할 때마다 이메일을 확인해야 함 |
Next.js나 다른 SSR 프레임워크를 사용한다면 다음 설정 체크리스트를 확인하세요.
detectSessionInUrl: true— Supabase가 URL에서 session을 자동으로 추출하도록 설정flowType: 'pkce'— PKCE 흐름을 강제로 사용- 올바른
redirectTo설정 — callback route가 auth code를 정확히 처리할 수 있어야 함 - 환경 변수 확인 — NEXT_PUBLIC_SUPABASE_URL과 NEXT_PUBLIC_SUPABASE_ANON_KEY가 모두 설정되어 있는지 확인
마지막으로 자주 묻는 질문을 몇 가지 살펴보겠습니다.
Q: 로컬 개발 환경에서 OAuth가 계속 실패하는 이유는 무엇인가요?
대부분 callback URL이 잘못 설정된 경우입니다. Supabase Dashboard의 Provider callback 설정을 확인하고 프로덕션 도메인이 아닌 localhost 주소를 사용하는지 확인하세요.
Q: JWT가 만료되면 사용자가 강제로 로그아웃되나요?
클라이언트에 자동 갱신 기능이 있는지 확인하세요. Supabase의 onAuthStateChange 리스너가 token 갱신을 자동으로 처리하므로 갱신 로직을 직접 작성할 필요는 없습니다.
Q: 세션이 갑자기 사라지면 어떻게 해야 하나요?
SSR 환경에서 흔히 발생하는 문제입니다. server client와 browser client가 모두 올바르게 초기화되었는지, 특히 cookies가 정상적으로 전달되는지 확인하세요.
결론
Supabase Auth의 세 가지 인증 방식은 각각 쓰임새가 다릅니다. 이메일 인증은 정식 가입 절차에 적합한 기본 방식입니다. OAuth는 사용자 경험을 개선하며 빠른 로그인이 중요한 환경에 알맞습니다. 세션 관리는 이 모든 것을 뒷받침하는 기반으로, SSR 환경에서 정확히 설정하려면 JWT와 PKCE 흐름을 이해해야 합니다.
제가 겪었던 문제는 사실 모두 단순했습니다. callback URL을 잘못 입력하거나, confirm email 스위치를 켜는 것을 잊거나, PKCE 흐름을 설정하지 않은 경우였습니다. 이런 세부 사항만 정확히 파악하면 인증 시스템을 안정적으로 운영할 수 있습니다.
다음 단계로 Auth 설정을 마쳤다면 Row Level Security(RLS)로 데이터를 보호하는 것도 잊지 마세요. Supabase의 RLS는 Auth와 긴밀하게 연결되어 각 사용자가 자신의 데이터에만 접근하도록 합니다. 여기까지 갖춰야 완전한 인증 시스템이라고 할 수 있습니다.
Supabase Auth 전체 흐름 설정하기
이메일 인증부터 OAuth 연동, SSR 환경의 PKCE 설정까지 다룹니다
⏱️ Estimated time: 30 min
- 1
Step 1: 이메일 인증 활성화
Supabase Dashboard에서 다음과 같이 설정합니다.
• Authentication → Providers → Email로 이동
• Confirm Email 스위치 활성화
• emailRedirectTo 매개변수를 애플리케이션 콜백 주소로 설정
• 로컬 개발 환경에서는 Mailcatcher로 테스트 메일 확인 - 2
Step 2: GitHub OAuth 설정
GitHub와 Supabase 사이에 OAuth 연결을 구성합니다.
• GitHub에서 OAuth App 생성(Settings → Developer settings → OAuth Apps)
• Callback URL 입력: https://<ref>.supabase.co/auth/v1/callback
• 로컬 개발 환경에서는 http://localhost:54321/auth/v1/callback 사용
• Client ID와 Client Secret을 Supabase Dashboard에 복사 - 3
Step 3: PKCE 흐름 설정
SSR 환경(Next.js)에 안전한 인증 흐름을 설정합니다.
• 클라이언트 초기화 시 flowType: 'pkce' 설정
• detectSessionInUrl: true 활성화
• code 교환을 처리할 /auth/callback 라우트 생성
• auth code의 유효 기간은 5분이며 한 번만 사용 가능 - 4
Step 4: 세션 갱신 처리
세션이 계속 유효하도록 관리합니다.
• access token은 기본적으로 1시간 후 만료
• refresh token은 일회용이며 재사용 허용 시간은 10초
• 클라이언트에서 onAuthStateChange를 수신해 자동 갱신
• SSR 환경에서 cookies가 올바르게 전달되는지 확인
FAQ
Supabase Auth는 어떤 OAuth Provider를 지원하나요?
JWT access token의 기본 만료 시간은 얼마인가요?
SSR 환경에서 PKCE 흐름을 반드시 사용해야 하는 이유는 무엇인가요?
로컬 개발 환경에서 OAuth callback이 계속 실패하면 어떻게 해야 하나요?
refresh token 재사용 허용 시간이란 무엇인가요?
세 가지 인증 방식은 어떻게 선택해야 하나요?
2분 읽기 · 게시일: 2026년 4월 8일 · 수정일: 2026년 9월 4일
Supabase 실전
검색으로 들어왔다면 같은 시리즈의 이전 글이나 다음 글로 이동하는 것이 가장 빠릅니다.
이전
Supabase 데이터베이스 설계 완벽 가이드: 테이블 구조, 관계, Row Level Security
Supabase 데이터베이스 설계 모범 사례를 자세히 설명합니다. 테이블 구조 명명 규칙, 세 가지 관계 모델, Row Level Security 정책 설계와 성능 최적화 방법을 실전 사례와 함께 살펴봅니다.
8편 중 2편
다음
Supabase Storage 실전: 파일 업로드, 권한 제어, CDN 가속
Supabase Storage에서 파일 업로드, RLS Policy 기반 사용자 격리, Smart CDN 연동, 이미지 변환까지 설정하는 전체 과정을 설명합니다.
8편 중 4편



댓글
GitHub로 로그인하여 댓글을 남기세요