회원가입 진행중
This commit is contained in:
19
utils/supabase/client.ts
Normal file
19
utils/supabase/client.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createBrowserClient } from "@supabase/ssr";
|
||||
|
||||
/**
|
||||
* [클라이언트 컴포넌트용 Supabase 클라이언트 생성 함수]
|
||||
*
|
||||
* 이 함수는 브라우저(Front-end)에서 동작하는 컴포넌트(useEffect, onClick 등)에서 사용합니다.
|
||||
* @supabase/ssr 패키지의 createBrowserClient를 사용하면 알아서 브라우저 쿠키를 관리해줍니다.
|
||||
*/
|
||||
export function createClient() {
|
||||
/**
|
||||
* createBrowserClient: 브라우저 환경에 최적화된 싱글톤(Singleton) 클라이언트를 반환합니다.
|
||||
* - 브라우저는 보안상 'service_role' 같은 비밀 키를 절대 사용하면 안 됩니다.
|
||||
* - 반드시 'NEXT_PUBLIC_'으로 시작하는 URL과 ANON KEY만 사용해야 합니다.
|
||||
*/
|
||||
return createBrowserClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||
);
|
||||
}
|
||||
88
utils/supabase/middleware.ts
Normal file
88
utils/supabase/middleware.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { createServerClient } from "@supabase/ssr";
|
||||
import { NextResponse, type NextRequest } from "next/server";
|
||||
|
||||
/**
|
||||
* [미들웨어용 세션 업데이트 및 라우트 보호 함수]
|
||||
*
|
||||
* 이 함수는 모든 페이지 요청이 서버에 도달하기 전에 '가장 먼저' 실행됩니다.
|
||||
*
|
||||
* 주요 기능:
|
||||
* 1. 사용자의 로그인 토큰이 만료되었는지 확인하고, 만료되었다면 자동으로 갱신(Refresh)
|
||||
* 2. 인증 상태에 따라 접근 가능한 페이지를 제어 (라우트 보호)
|
||||
*/
|
||||
export async function updateSession(request: NextRequest) {
|
||||
// 1. 초기 응답 객체를 생성합니다. (그냥 통과시키는 기본 응답)
|
||||
let supabaseResponse = NextResponse.next({
|
||||
request,
|
||||
});
|
||||
|
||||
// 2. 미들웨어 환경에서 동작하는 Supabase 클라이언트를 생성합니다.
|
||||
const supabase = createServerClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
{
|
||||
cookies: {
|
||||
// 요청(Request)에서 쿠키를 읽어옵니다.
|
||||
getAll() {
|
||||
return request.cookies.getAll();
|
||||
},
|
||||
// 쿠키를 저장/수정할 때 사용하는 로직입니다.
|
||||
setAll(cookiesToSet) {
|
||||
// (1) 요청(Request) 객체에도 쿠키를 업데이트해줍니다. (서버 컴포넌트가 최신 상태를 알도록)
|
||||
cookiesToSet.forEach(({ name, value }) =>
|
||||
request.cookies.set(name, value),
|
||||
);
|
||||
|
||||
// (2) 응답(Response) 객체는 새로 만들어줍니다.
|
||||
supabaseResponse = NextResponse.next({
|
||||
request,
|
||||
});
|
||||
|
||||
// (3) 최종 응답(Response)에 쿠키를 심어서 브라우저로 보냅니다.
|
||||
cookiesToSet.forEach(({ name, value, options }) =>
|
||||
supabaseResponse.cookies.set(name, value, options),
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// 3. [핵심] 사용자의 인증 정보를 가져옵니다.
|
||||
// 이 함수를 호출하는 것만으로 Supabase는 만료된 토큰을 자동으로 '재발급(Refresh)' 합니다.
|
||||
// 재발급된 쿠키는 위 setAll 로직을 통해 Response에 담겨 브라우저로 전달됩니다.
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
console.log(user);
|
||||
// 4. [라우트 보호] 인증 상태에 따라 접근 제어
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
// 4-1. 인증 관련 페이지 목록 (로그인하지 않아도 접근 가능)
|
||||
// 로그인, 회원가입, 비밀번호 찾기, 비밀번호 재설정 페이지
|
||||
const authPages = [
|
||||
"/login",
|
||||
"/signup",
|
||||
"/forgot-password",
|
||||
"/reset-password",
|
||||
];
|
||||
const isAuthPage = authPages.some((page) => pathname.startsWith(page));
|
||||
|
||||
// 4-2. 로그인하지 않은 사용자가 보호된 페이지에 접근하려는 경우
|
||||
// → /login 페이지로 리다이렉트
|
||||
if (!user && !isAuthPage) {
|
||||
const loginUrl = new URL("/login", request.url);
|
||||
return NextResponse.redirect(loginUrl);
|
||||
}
|
||||
|
||||
// 4-3. 이미 로그인한 사용자가 인증 페이지(/login, /signup)에 접근하려는 경우
|
||||
// → 메인 페이지(/)로 리다이렉트 (불필요한 접근 방지)
|
||||
// 단, /reset-password는 예외 (이메일 링크를 통한 비밀번호 재설정 허용)
|
||||
if (user && isAuthPage && pathname !== "/reset-password") {
|
||||
const homeUrl = new URL("/", request.url);
|
||||
return NextResponse.redirect(homeUrl);
|
||||
}
|
||||
|
||||
// 5. 갱신된 쿠키가 담긴 응답을 반환하여 다음 단계(페이지 렌더링)로 넘어갑니다.
|
||||
return supabaseResponse;
|
||||
}
|
||||
47
utils/supabase/server.ts
Normal file
47
utils/supabase/server.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { createServerClient } from "@supabase/ssr";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
/**
|
||||
* [서버 컴포넌트용 Supabase 클라이언트 생성 함수]
|
||||
*
|
||||
* 이 함수는 Next.js의 SSR(서버 사이드 렌더링) 환경에서 Supabase에 접근할 때 사용합니다.
|
||||
* 서버 컴포넌트, 서버 액션(Server Actions), 라우트 핸들러(Route Handlers)에서 호출됩니다.
|
||||
*/
|
||||
export async function createClient() {
|
||||
// Next.js의 쿠키 저장소에 접근합니다. (await 필수)
|
||||
const cookieStore = await cookies();
|
||||
|
||||
/**
|
||||
* createServerClient: 서버 환경에서 안전하게 Supabase 클라이언트를 생성합니다.
|
||||
* 첫 번째 인자: Supabase 프로젝트 URL
|
||||
* 두 번째 인자: Supabase 익명(Anon) 키 (공개되어도 안전한 키)
|
||||
* 세 번째 인자: 쿠키 제어 옵션
|
||||
*/
|
||||
return createServerClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
{
|
||||
cookies: {
|
||||
// 1. Supabase가 쿠키를 읽어야 할 때 실행됩니다.
|
||||
// 현재 요청(Request)에 있는 모든 쿠키를 가져와서 Supabase에 전달합니다.
|
||||
getAll() {
|
||||
return cookieStore.getAll();
|
||||
},
|
||||
|
||||
// 2. Supabase가 쿠키를 새로 써야 할 때(로그인, 로그아웃, 토큰 갱신 등) 실행됩니다.
|
||||
setAll(cookiesToSet) {
|
||||
try {
|
||||
// Supabase가 요청한 쿠키들을 하나씩 브라우저에 저장하도록 설정합니다.
|
||||
cookiesToSet.forEach(({ name, value, options }) =>
|
||||
cookieStore.set(name, value, options)
|
||||
);
|
||||
} catch {
|
||||
// [주의] 이 부분은 '서버 컴포넌트'에서 쿠키를 쓰려고 할 때 발생하는 에러를 무시하기 위함입니다.
|
||||
// Next.js 규칙상 '서버 컴포넌트'는 렌더링 중에 쿠키를 직접 쓸 수 없습니다.
|
||||
// 대신 미들웨어(middleware)가 토큰 갱신을 담당하므로 여기서는 에러를 무시해도 안전합니다.
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user