89 lines
3.5 KiB
TypeScript
89 lines
3.5 KiB
TypeScript
|
|
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;
|
||
|
|
}
|