[Next.js] Supabase 인증을 Middleware(Proxy)로 관리하기

김태민·2026년 2월 4일

기존 상황

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.tsproxy.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를 쿠키 기반으로 변경해야 합니다.

2. 쿠키 기반 Supabase 클라이언트 생성

import { createBrowserClient } from "@supabase/ssr";

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
}

3. 로그인/회원가입 페이지 수정

// 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
  });
};

4. Middleware 작성

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: [],
};

5. Admin 페이지에서 기존 권한 인증 과정 삭제

6. 경로별 리다이렉트 분기

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를 활용하여 안전하고 효율적인 인증 시스템을 구축할 수 있었습니다.

profile
빠르게 성장하는 개발자

0개의 댓글