EzySalad 프로젝트에는 단 하나의 인증이 필요한 페이지가 존재했습니다. 바로 admin 페이지입니다. 이 페이지에서는 메뉴 CRUD, 사진 추가 등 관리자 기능을 제공했습니다.
단 하나의 페이지에서만 인증이 필요했기 때문에, Supabase의 Auth 기능을 이용하여 Admin 페이지에서 직접 권한 처리를 구현했습니다.
const checkUser = useCallback(async () => {
try {
const supabase = createClient();
const {
data: { user },
error,
} = await supabase.auth.getUser();
if (error || !user) {
router.push("/thisisforadmin/login");
return;
}
// 관리자 권한 확인
const adminEmail = process.env.NEXT_PUBLIC_ADMIN_EMAIL;
if (user.email !== adminEmail) {
await supabase.auth.signOut();
router.push("/thisisforadmin/login");
return;
}
이제 인증이 한 개의 페이지에서만 필요하지 않았습니다. EzySalad에서 대학생들을 대상으로 단체 주문을 받을 예정이기 때문에 회원가입이 필요했고, 이에 따라 페이지 별 접근 권한 제어가 필요했기 때문입니다.
물론, 기존 방식대로 페이지 별로 권한 체크를 할 수도 있었겠지만, 추후에 학교가 늘어나거나 규모가 커질 것을 대비하여 Proxy(Middleware)를 구성하는 것이 낫겠다고 판단했습니다.
Next.js 16부터 middleware.ts가 proxy.ts로 변경되었습니다:
https://nextjs.org/docs/messages/middleware-to-proxy
문제: 기존에는 Supabase가 localStorage에 세션을 저장하고 있었습니다.
// lib/supabase.ts (localStorage 기반)
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(url, key);
// 기본값: localStorage 사용
문제: Middleware는 서버에서 실행되므로 브라우저 API인 localStorage에 접근할 수 없습니다.
해결: Supabase를 쿠키 기반으로 변경해야 합니다.
import { createBrowserClient } from "@supabase/ssr";
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
}
// Before
import { supabase } from "@/../lib/supabase";
const handleLogin = async () => {
const { data, error } = await supabase.auth.signInWithPassword({
email, password
});
};
// After
import { createClient } from "@/../lib/supabase/client";
const handleLogin = async () => {
const supabase = createClient();
const { data, error } = await supabase.auth.signInWithPassword({
email, password
});
};
import { createServerClient } from "@supabase/ssr";
import { NextResponse, type NextRequest } from "next/server";
export async function middleware(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 }) =>
request.cookies.set(name, value)
);
supabaseResponse = NextResponse.next({ request });
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
);
},
},
}
);
// 쿠키에서 사용자 확인
const { data: { user } } = await supabase.auth.getUser();
// 보호할 경로 체크
const protectedPaths = [];
const isProtectedPath = protectedPaths.some((path) =>
request.nextUrl.pathname.startsWith(path)
);
// 인증되지 않은 사용자는 로그인 페이지로
if (isProtectedPath && !user) {
return NextResponse.redirect(new URL("/university/login", request.url));
}
return supabaseResponse;
}
export const config = {
matcher: [],
};
if (isProtectedPath && !user) {
// Admin 경로는 admin 로그인으로
if (request.nextUrl.pathname.startsWith("/thisisforadmin") ||
request.nextUrl.pathname.startsWith("/api/admin")) {
return NextResponse.redirect(
new URL("/thisisforadmin/login", request.url)
);
}
// University 경로는 university 로그인으로
return NextResponse.redirect(
new URL("/university/login", request.url)
);
}
각 페이지에서 개별적으로 인증을 체크하던 방식에서 Middleware(Proxy)로 중앙 집중식 관리로 전환했습니다.
Supabase의 쿠키 기반 인증과 Next.js 16의 Proxy를 활용하여 안전하고 효율적인 인증 시스템을 구축할 수 있었습니다.