반응형
최종 전 마지막 프로젝트 드디어 끝-!
오늘은 조금 아쉬웠던 auth에 대해 정리해보고자 한다.
챌린지반 강의정리 - Supabase + Next.js 인증 및 데이터 관리 개요
나중에 써먹어보고 싶어서 적어보는 Auth에 대한 정리!
디렉토리 구조 및 환경 변수 설정
1. 환경 변수 설정
프로젝트 루트에 .env.local 파일 생성
NEXT_PUBLIC_SUPABASE_URL=<your_supabase_project_url>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<your_supabase_anon_key>
2. 추천 디렉토리 구조
utils/
supabase/
client.ts # 클라이언트용 Supabase 클라이언트
server.ts # 서버용 Supabase 클라이언트
middleware.ts # 인증 상태 관리 미들웨어
app/
login/
page.tsx
private/
page.tsx
auth/
confirm/
route.ts
middleware.ts
Supabase 클라이언트 설정
1. 클라이언트용 Supabase 클라이언트 (utils/supabase/client.ts)
import { createBrowserClient } from "@supabase/ssr";
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
}
2. 서버용 Supabase 클라이언트 (utils/supabase/server.ts)
서버 컴포넌트, Route Handler에서 Supabase를 사용 시, createServerClient를 사용
cookies 객체를 통해 인증 세션을 관리하고, 요청마다 독립적인 클라이언트를 생성
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
export async function createClient() {
const cookieStore = cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll: () => cookieStore.getAll(),
setAll: (cookiesToSet) =>
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
),
},
}
);
}
cookies 객체 역할
- 사용자가 로그인하면 인증 토큰이 생성
- 이 토큰을 쿠키에 저장하고, 다음에 페이지를 열면 쿠키에 있는 토큰을 통해 로그인 상태를 유지
- Supabase는 cookies 객체를 통해 쿠키 읽기/쓰기 방식을 이해하고, 인증 상태를 관리
- 로그인 및 회원가입 예제 (서버 컴포넌트)
"use server";
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { createClient } from '@/utils/supabase/server';
export async function login(formData: FormData) {
const supabase = await createClient();
const data = {
email: formData.get('email') as string,
password: formData.get('password') as string,
};
const { error } = await supabase.auth.signInWithPassword(data);
if (error) redirect('/error');
revalidatePath('/', 'layout');
redirect('/');
}
export async function signup(formData: FormData) {
const supabase = await createClient();
const data = {
email: formData.get('email') as string,
password: formData.get('password') as string,
};
const { error } = await supabase.auth.signUp(data);
if (error) redirect('/error');
revalidatePath('/', 'layout');
redirect('/');
}
- Route Handler에서 Supabase 사용 예시
import { createClient } from "@/utils/supabase/server";
export async function POST(request: Request) {
const supabase = await createClient();
const body = await request.json();
const { data, error } = await supabase.from("todos").insert(body);
if (error) {
return new Response(JSON.stringify({ error: error.message }), { status: 400 });
}
return new Response(JSON.stringify({ data }), { status: 200 });
}
미들웨어 설정 (세션 갱신)
1. 미들웨어 로직 (utils/supabase/middleware.ts)
import { createServerClient } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';
export async function updateSession(request: NextRequest) {
let supabaseResponse = 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, options }) =>
request.cookies.set(name, value)
);
},
},
}
);
const { data: { user } } = await supabase.auth.getUser();
if (!user && !request.nextUrl.pathname.startsWith("/login")) {
const url = request.nextUrl.clone();
url.pathname = "/login";
return NextResponse.redirect(url);
}
if (user && request.nextUrl.pathname.startsWith("/login")) {
return NextResponse.redirect('/');
}
return supabaseResponse;
}
- 미들웨어 적용 (middleware.ts)
import { type NextRequest } from 'next/server';
import { updateSession } from '@/utils/supabase/middleware';
export async function middleware(request: NextRequest) {
return await updateSession(request);
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};
- 트리거를 사용한 데이터 동기화
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.users (id, email)
VALUES (NEW.id, NEW.email);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW
EXECUTE FUNCTION public.handle_new_user();
TypeScript 타입 연동
supabase gen types typescript --project-id $PROJECT_REF > src/types/supabase.ts
import { Database } from "@/types/supabase";
type Todo = Database["public"]["Tables"]["todos"]["Row"];
const [todo, setTodo] = useState<Todo | null>(null);
728x90
반응형
'React TIL' 카테고리의 다른 글
[React] Day_73 심화 프로젝트 후기 (1) | 2025.01.02 |
---|---|
[React] Day_72 데일리 정리 (0) | 2024.12.31 |
[React] Day_70 오늘의 정리 (0) | 2024.12.27 |
[React] Day_69 팀 프로젝트 작업 관련 트러블슈팅 (1) (0) | 2024.12.26 |
[React] Day_68 데일리 정리 (1) | 2024.12.24 |