
Supabase Auth 로그인 구현 - 10편. 회원가입부터 로그아웃까지
Supabase Auth와 Server Action으로 인증 시스템을 구축합니다.
이 글에서는 회원가입, 로그인, 로그아웃, 세션 관리, 보안 팁을 다룹니다.
🔐 드디어 로그인 만들기
DB 설계와 기반 시스템이 갖춰졌습니다. 이제 사용자를 받을 차례입니다.
로그인 기능이 없으면 누가 누군지 모릅니다. 문의를 누가 했는지, 견적을 누구에게 보낼지 알 수가 없습니다.
인증(Authentication)이 필요했습니다.
Claude에게 물었습니다.
Supabase로 로그인 기능을 만들 수 있어?Supabase Auth가 내장되어 있어서 별도 서비스 없이 바로 쓸 수 있다고 했습니다.
🎯 Supabase Auth의 장점
Firebase Auth와 비슷하지만, 몇 가지 장점이 있었습니다.
- Supabase에 내장되어 있음 (별도 설정 불필요)
- 이메일/비밀번호, OAuth(Google, GitHub 등) 지원
- 세션 관리 자동화
- RLS와 연동 가능
특히 RLS와 연동된다는 게 좋았습니다. "이 사용자는 자기 데이터만 볼 수 있다" 같은 규칙을 DB 레벨에서 적용할 수 있으니까요.
📝 회원가입 구현
먼저 회원가입부터 만들었습니다.
스키마 정의
Zod로 검증 스키마를 만들었습니다.
// lib/validations/auth.ts
import { z } from 'zod'
export const signupSchema = z.object({
email: z
.string()
.min(1, '이메일을 입력하세요')
.email('올바른 이메일 형식이 아닙니다'),
password: z
.string()
.min(1, '비밀번호를 입력하세요')
.min(8, '비밀번호는 8자 이상이어야 합니다'),
passwordConfirm: z.string(),
name: z.string().min(1, '이름을 입력하세요'),
phone: z.string().optional(),
company: z.string().optional(),
}).refine((data) => data.password === data.passwordConfirm, {
message: '비밀번호가 일치하지 않습니다',
path: ['passwordConfirm'],
})
export type SignupFormData = z.infer<typeof signupSchema>
Server Action
Claude에게 Server Action으로 회원가입 로직을 요청했습니다.
// app/actions/auth.ts
'use server'
import { createClient } from '@/lib/supabase/server'
import { signupSchema } from '@/lib/validations/auth'
export async function signup(formData: FormData) {
const supabase = await createClient()
const rawData = {
email: formData.get('email') as string,
password: formData.get('password') as string,
passwordConfirm: formData.get('passwordConfirm') as string,
name: formData.get('name') as string,
phone: formData.get('phone') as string,
company: formData.get('company') as string,
}
const result = signupSchema.safeParse(rawData)
if (!result.success) {
return { error: result.error.errors[0].message }
}
const { email, password, name, phone, company } = result.data
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
data: { name, phone, company }
}
})
if (error) {
return { error: error.message }
}
return { success: true }
}
Supabase Auth의 signUp 함수 하나로 끝납니다. options.data에 추가 정보도 넣을 수 있습니다.
🚪 로그인 구현
회원가입보다 더 간단했습니다.
// app/actions/auth.ts
export async function login(formData: FormData) {
const supabase = await createClient()
const email = formData.get('email') as string
const password = formData.get('password') as string
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) {
return { error: '이메일 또는 비밀번호가 올바르지 않습니다' }
}
return { success: true }
}
signInWithPassword 함수 하나로 로그인이 됩니다.
로그인 페이지
'use client'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { login } from '@/app/actions/auth'
import { useRouter } from 'next/navigation'
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(1),
})
type LoginFormData = z.infer<typeof loginSchema>
export default function LoginPage() {
const router = useRouter()
const form = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
})
const onSubmit = async (data: LoginFormData) => {
const formData = new FormData()
formData.append('email', data.email)
formData.append('password', data.password)
const result = await login(formData)
if (result.error) {
form.setError('root', { message: result.error })
return
}
router.push('/mypage')
}
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* 폼 필드들... */}
</form>
)
}
🚶 로그아웃
로그아웃은 더 간단했습니다.
// app/actions/auth.ts
export async function logout() {
const supabase = await createClient()
await supabase.auth.signOut()
redirect('/login')
}
// 버튼에서 사용
<form action={logout}>
<button type="submit">로그아웃</button>
</form>
Server Action을 form action으로 바로 쓸 수 있어서 편했습니다.
👤 현재 사용자 가져오기
로그인한 사용자 정보가 필요할 때가 많습니다.
서버 컴포넌트에서
import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'
export default async function MyPage() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
redirect('/login')
}
return (
<div>
<h1>{user.email}님, 안녕하세요!</h1>
<p>이름: {user.user_metadata.name}</p>
</div>
)
}
클라이언트 컴포넌트에서
'use client'
import { createClient } from '@/lib/supabase/client'
import { useEffect, useState } from 'react'
import { User } from '@supabase/supabase-js'
export function UserInfo() {
const [user, setUser] = useState<User | null>(null)
const supabase = createClient()
useEffect(() => {
supabase.auth.getUser().then(({ data }) => {
setUser(data.user)
})
}, [])
if (!user) return null
return <span>{user.email}</span>
}
🔄 세션 동기화
사용자가 다른 탭에서 로그아웃하면 어떻게 될까요?
현재 탭은 로그인 상태라고 생각합니다. 세션 변화를 감지해야 합니다.
// components/AuthProvider.tsx
'use client'
import { createClient } from '@/lib/supabase/client'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
export function AuthProvider({ children }: { children: React.ReactNode }) {
const router = useRouter()
const supabase = createClient()
useEffect(() => {
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
if (event === 'SIGNED_OUT') {
router.push('/login')
}
if (event === 'SIGNED_IN') {
router.refresh()
}
}
)
return () => {
subscription.unsubscribe()
}
}, [])
return <>{children}</>
}
onAuthStateChange로 세션 변화를 구독합니다. 로그아웃되면 로그인 페이지로, 로그인되면 화면을 새로고침합니다.
⚠️ 삽질했던 것들
로그인 후 세션이 없음
로그인은 성공하는데, 마이페이지에서 사용자 정보가 안 나왔습니다.
쿠키가 제대로 설정 안 된 거였습니다. 미들웨어에서 세션을 갱신해야 합니다.
// middleware.ts
export async function middleware(request: NextRequest) {
const response = NextResponse.next()
const supabase = createServerClient(...)
await supabase.auth.getSession() // 이게 중요!
return response
}
회원가입 후 바로 로그인 안 됨
Supabase 기본 설정이 이메일 인증을 요구합니다.
개발 중에는 Supabase 대시보드에서 "Confirm email"을 꺼두면 됩니다. 배포할 때는 다시 켜야 합니다.
💡 보안 팁
Claude가 알려준 것들입니다.
비밀번호 규칙 강화
password: z
.string()
.min(8, '8자 이상')
.regex(/[A-Z]/, '대문자 포함')
.regex(/[a-z]/, '소문자 포함')
.regex(/[0-9]/, '숫자 포함')
HTTPS 필수
배포 시 반드시 HTTPS를 사용해야 합니다. Vercel은 자동으로 적용됩니다.
CAPTCHA 추가
스팸 가입을 막으려면 CAPTCHA를 넣는 게 좋습니다. 14편에서 다룹니다.
📋 이번 편 요약
| 항목 | 내용 |
|---|---|
| 다룬 기능 | 회원가입, 로그인, 로그아웃, 세션 관리 |
| 사용 기술 | Supabase Auth, Server Action, Zod |
| 핵심 파일 | app/actions/auth.ts, lib/supabase/server.ts |
| 보안 팁 | 비밀번호 규칙 강화, CAPTCHA, HTTPS |
다음 편에서는 이메일 인증과 비밀번호 찾기를 구현합니다.
💬 인증 관련 질문 있으면 댓글로 남겨주세요!
'바이브코딩' 카테고리의 다른 글
| 12. 인증과 권한 체크 구현 - Next.js 미들웨어 페이지 보호 (0) | 2026.01.19 |
|---|---|
| 11. 회원가입과 비밀번호 찾기 - Supabase 이메일 인증 구현 (0) | 2026.01.18 |
| 16. 제품 선택부터 이메일 발송까지 - React Hook Form 문의 폼 구현 (0) | 2026.01.17 |
| 09. 한국어/일본어 지원-next-intl 다국어 설정 (0) | 2026.01.16 |
| 08. 타입 안전 폼 검증 - React Hook Form + Zod (0) | 2026.01.15 |