[기능] Next.js Middleware + Zustand persist로 인증/권한 관리하기

짜장킴·2025년 9월 13일

프로젝트

목록 보기
20/38

문제

  • 새로고침 시 로그인 상태가 초기화됨 → Local state만 사용한 한계
  • 인증 없이 보호 라우트에 접근 가능
  • 세션 지속성이 짧아져, 토큰 만료(401) 시 일정 주기로 재로그인을 유도해야 함

분석

  • 상태 영속화 필요: accessToken을 안전하게 저장해야 함
  • 서버 경계 차단 필요: 라우팅 단계(요청 시점)에서 인증/권한 판별
  • 요청 단위 인증 필요: 모든 API 요청에 헤더 자동 주입 + 401 에러 공통 처리

해결 방법

  • Zustand persist → 토큰·사용자 정보 영속화
  • 쿠키(accessToken) → Middleware에서 라우팅 시점에 인증 판별
  • Axios 인터셉터 → 요청 헤더 자동 주입 & 401 공통 처리
  • Next.js Middleware → 게스트/보호 라우트 분기

Zustand: 인증 상태 영속화

  • persist(LocalStorage): 새로고침에도 로그인 유지
  • 쿠키 동기화: 서버 미들웨어가 토큰을 읽어 인증 판별 가능
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { setCookie, deleteCookie } from "@/lib/utils/cookie";

interface AuthState {
  accessToken: string | null;
  userId: number | null;
  setAuth: (token: string, userId: number) => void;
  clearAuth: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      accessToken: null,
      userId: null,

      setAuth: (token, userId) =>
        set(() => {
          setCookie("accessToken", token); // ✅ 미들웨어가 읽을 수 있게 쿠키에 복제
          return { accessToken: token, userId };
        }),

      clearAuth: () =>
        set(() => {
          deleteCookie("accessToken");
          return { accessToken: null, userId: null };
        }),
    }),
    { name: "auth-storage" } // ✅ LocalStorage key
  )
);

Middleware: 라우트 보호

  • 요청 시점에서 접근 차단 → 클라이언트 JS 실행 전 미인증 접근 차단
  • FOUC 제거 (보호 페이지가 잠깐 보였다 사라지는 문제 해결)
export function middleware(request: NextRequest) {
  const token = request.cookies.get("accessToken")?.value;
  const { pathname } = request.nextUrl;

  const isAuthPage = pathname.startsWith("/login") || pathname.startsWith("/signup");
  const isPublicPage = pathname === "/" || isAuthPage;

  if (!token && !isPublicPage) {
    return NextResponse.redirect(new URL("/login", request.url)); // 게스트 차단
  }
  if (token && isAuthPage) {
    return NextResponse.redirect(new URL("/mydashboard", request.url)); // 인증자 차단
  }

  return NextResponse.next();
}

FOUC (Flash of Unstyled Content)란?

  • 브라우저가 HTML을 먼저 그린 뒤, CSS나 JS가 늦게 로딩되면서 짧은 순간 '스타일 안 입혀진 날 것의 화면'이 번쩍 보이는 현상

Axios Interceptor: 요청 단위 인증 처리

  • 요청 시: Authorization 헤더 자동 주입
  • 응답 시: 401 응답 → 자동 로그아웃 + 로그인 화면으로 전환
  • 중복 코드 제거 및 에러 처리 일관성 확보
import axios from "axios";
import { useAuthStore } from "@/lib/stores/auth";

export const api = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_BASE_URL || "",
});

export const attachInterceptors = (router: { push: (s: string) => void }) => {
  const reqId = api.interceptors.request.use((config) => {
    const token = useAuthStore.getState().accessToken;
    if (token) config.headers.Authorization = `Bearer ${token}`;
    return config;
  });

  const resId = api.interceptors.response.use(
    (res) => res,
    (err) => {
      if (err.response?.status === 401) {
        useAuthStore.getState().clearAuth();
        router.push("/login");
      }
      return Promise.reject(err);
    }
  );
  return () => {
    api.interceptors.request.eject(reqId);
    api.interceptors.response.eject(resId);
  };
};

개선 효과

  • 사용자 경험(UX)
    - 새로고침 후에도 로그인 상태 유지 → 재로그인 빈도 감소
    - 보호 페이지 접근 시 즉시 리다이렉트 → 깜빡임 없는 전환
    - 세션 만료시 자동 로그아웃 + 로그인 페이지 이동으로 혼란 최소화
  • 보안
    - 클라이언트 렌더 이전 단계에서 인증 차단 → 민감 페이지 노출 방지
    - 쿠키 기반 인증 검증으로 라우트 접근 제어 강화
  • 유지보수성
    - 인증 로직이 상태 / 라우팅 / 네트워킹 레이어로 분리
    - Axios 인터셉터로 중복 제거 → 코드 일관성 확보
profile
프론트엔드

0개의 댓글