[Middleware] 임시 로그인 로직 리팩토링(local storage → httpOnly 쿠키)

Innes·2025년 6월 22일
0

임시 로그인 로직 리팩토링 과정 (local storage → httpOnly 쿠키)

📝 백엔드로부터 로그인 api를 받기 전, 프론트에서 권한 별 페이지 접근 제어를 해야 해서 임시 로그인을 구현하게 되었다.
처음에는 local storage로 임시 키를 넣었다 빼는 로직이 간편하니까 그렇게 진행했는데, 이후 미들웨어를 사용하면서 문제가 생겼다.
아래는 local storage에서 httpOnly 쿠키로 임시 로그인 로직을 리팩토링한 과정을 작성해보았다.

기술 스택

Next.js 프로젝트라서 httpOnly 쿠키를 주고 받는 백엔드 api를 임시로 작성할 수 있었다.

리팩토링 과정

  1. 처음엔 local storage에 값을 넣었다 뺐다 하는게 쉬우니까 그걸로 임시 로그인 로직을 구현해놨었음
  2. 사이드바 만들다보니 어쩔수없이(?) 인증 상태별 접근권한 설정이 필요해 구현
    1. 로그인 상태일때만 사이드바, 각 메뉴별 페이지가 보이도록 만들어야 했음
    2. local storage, zustand로 로그인 상태 판별
    3. ‘/’, dashboard, login, layout.tsx, RootPage 에서 각자 로그인 상태 확인해서 리다이렉트 시켜주는 아주 복잡하고 파악안되는 거미줄이 되어버림
    4. → 미들웨어를 써서 컴포넌트 책임분리 및 가독성 향상 등등등을 노려야겠군!
    • 사이드바 및 기본 인증 시스템 구현 (구 버전) SPRINT5.md
      # SPRINT 5: 사이드바 및 기본 인증 시스템 구현
      
      ## 주요 목표
      
      - Figma 디자인 기반의 사이드바 UI 컴포넌트 개발
      - 임시 로그인/로그아웃 기능 및 전역 상태 관리 시스템 구축
      - 사용자의 인증 상태에 따라 동적으로 라우팅 및 레이아웃을 제어하는 로직 구현
      
      ---
      
      ## ✅ 완료된 작업
      
      ### 1. 사이드바 UI 및 기능 구현
      
      - **Figma 분석 및 아이콘 에셋 추출**:
        - Figma 디자인을 분석하여 사이드바 레이아웃과 메뉴 구조를 파악했습니다.
        - 메뉴에 필요한 모든 아이콘을 SVG 형식으로 추출하여 `src/assets/svg/sidebar` 디렉토리에 저장했습니다.
        - `svgr` 라이브러리를 설정하여 SVG 아이콘을 React 컴포넌트로 직접 불러와 사용했습니다.
      
      - **`Sidebar` 컴포넌트 개발 (`src/components/layout/Sidebar.tsx`)**:
        - `usePathname` 훅을 활용하여 현재 URL 경로를 감지하고, 활성화된 메뉴 항목에 동적으로 하이라이트 스타일(배경색 변경)을 적용하는 클라이언트 컴포넌트를 생성했습니다.
        - 메뉴 항목들을 `Main`, `Workflows`, `Other` 등 논리적 그룹으로 나누어 구조화하여 코드의 가독성과 유지보수성을 향상시켰습니다.
      
      ### 2. 임시 인증 시스템 구축
      
      - **Zustand를 이용한 전역 상태 관리 (`src/store/useAuthStore.ts`)**:
        - `isLoggedIn` (로그인 여부), `isLoading` (인증 상태 확인 중) 상태와 `login`, `logout`, `checkAuth` 액션을 포함하는 전역 스토어를 생성했습니다.
        - `localStorage`를 사용하여 브라우저를 새로고침해도 로그인 상태가 유지되도록 구현했습니다.
      
      - **인증 관련 유틸리티 컴포넌트 개발 (`src/components/auth/`)**:
        - `AuthInitializer.tsx`: 앱이 처음 로드될 때 `localStorage`를 확인하여 전역 스토어의 인증 상태를 초기화하는 컴포넌트입니다.
        - `AuthButton.tsx`: 개발 및 테스트 편의를 위해 UI에 영향을 주지 않는 임시 '로그인'/'로그아웃' 토글 버튼을 추가했습니다.
      
      ### 3. 인증 기반 동적 라우팅 및 레이아웃 적용
      
      - **인증된 사용자 전용 레이아웃 분리 (`src/app/(authenticated)/`)**:
        - Next.js의 Route Group 기능을 사용하여 URL에 영향을 주지 않는 `(authenticated)` 그룹을 생성했습니다.
        - `(authenticated)/layout.tsx`: 해당 그룹에 속한 페이지들을 위한 공통 레이아웃을 생성했습니다.
          - 이 레이아웃은 사용자가 로그인 상태일 때만 `Sidebar`와 페이지 콘텐츠를 보여줍니다.
          - 로그인되어 있지 않은 사용자가 접근할 경우, 자동으로 로그인 페이지(`/login`)로 리다이렉트 시키는 보호 로직을 구현했습니다.
        - `(authenticated)/dashboard/page.tsx`: 로그인 후 가장 먼저 보게 될 대시보드 페이지를 생성하여 보호된 라우트 그룹 내에 배치했습니다.
      
      - **페이지별 접근 제어 로직 구현**:
        - **루트 페이지 (`/`)**: 사용자의 인증 상태를 확인하여 `/dashboard` 또는 `/login`으로 자동 리다이렉트 시키는 역할로 변경했습니다.
        - **로그인 페이지 (`/login`)**: 이미 로그인한 사용자가 접근할 경우, 불필요한 로그인 과정을 생략하고 즉시 `/dashboard`로 이동시키는 로직을 추가했습니다.
      
      ## ⚙️ 기술 스택 변경 및 추가
      
      - **`zustand`**: 전역 상태 관리를 위해 도입
      - **`clsx`**: 조건부 스타일링의 가독성 향상을 위해 사용
      
      ## 💭 회고
      
      이번 스프린트를 통해 어드민 시스템의 핵심 기능인 사이드바와 인증 기반의 페이지 접근 제어 시스템의 기반을 마련했습니다. 특히 Zustand와 Next.js의 Route Group을 활용하여 효율적이고 확장 가능한 구조를 설계하는 데 집중했습니다. 현재 인증은 `localStorage`를 이용한 임시 방식이므로, 다음 스프린트에서는 실제 백엔드 API와 연동하여 안전한 인증 시스템을 구축하는 작업이 필요합니다. 
  3. 그래서 미들웨어로 접근권한 관리를 통일하려니까 갑자기 js-cookie인가 그 라이브러리를 설치해야된다그랬음
  4. 미들웨어는 서버에서 쓰는건데 local storage는 브라우저에서만 접근이 가능해서 해당 라이브러리로 로그인 확인을 해야된다고 했음
  5. 어차피 나중에 로그인 로직 백엔드랑 연결하면서 local storage도 안쓸거고, 아마 httpOnly 쿠키로 refresh token 갱신식으로 전달해주는 로직으로 전달해줄것 같은데, 그럴거면 이 임시 로그인 로직때문에 js-cookie 라이브러리까지 다운받아야된다고? 그건 안되지
  6. 그래서 임시 로그인 로직 자체를 바꾸는 방향으로 노선을 틀었음
    1. next.js는 백엔드 api 로직 구현도 가능해서 좋다! httpOnly 쿠키에 값 넣었다 뺐다 하는 서버 api들을 만듦
    2. zustand store에서 fetch로 httpOnly 쿠키에 접근하는 api에 연결하여 로그인 상태 확인하는 로직 구현!
  7. AuthInitializer.tsx까지 만들어서 앱 처음 로드시 서버 실제 쿠키 상태와 클라이언트의 useAuthStore를 일치시키는 ‘최초 동기화’ 작업 수행하도록 추가
  • 인증 로직 리팩토링 및 미들웨어 도입 SPRINT6.md
    # SPRINT 6: 인증 시스템 리팩토링 및 미들웨어 도입
    
    ## 주요 목표
    
    -   여러 컴포넌트에 흩어져 있던 인증 및 페이지 이동 로직을 **리팩토링**하여 중앙에서 관리
    -   Next.js 미들웨어를 도입하여 요청 단계에서 접근 제어를 처리하는 효율적이고 안전한 구조 구축
    -   `localStorage` 기반의 임시 인증을 `httpOnly` 쿠키 기반으로 변경하여 실제 백엔드 연동에 가까운 구조로 개선
    
    ---
    
    ## ✅ 완료된 작업: 인증 로직 리팩토링
    
    이번 스프린트는 새로운 기능 개발보다는, 기존에 구현했던 임시 인증 시스템의 구조적 문제점을 개선하는 **리팩토링**에 집중했습니다.
    
    ### 1. 인증 방식 변경: `localStorage`에서 `httpOnly` 쿠키로
    
    -   **문제점**: 기존 방식은 클라이언트 측 `localStorage`에 의존하여 미들웨어에서 접근이 불가능했고, 실제 프로덕션 인증 방식과 구조적 차이가 컸습니다.
    -   **리팩토링**:
        -   인증 상태를 관리하기 위한 임시 백엔드 API 라우트를 생성했습니다.
            -   `POST /api/auth/login`: 로그인 요청 시 `httpOnly` 속성을 가진 인증 쿠키를 생성합니다.
            -   `POST /api/auth/logout`: 인증 쿠키를 삭제합니다.
            -   `GET /api/auth/status`: 현재 쿠키 유무를 통해 로그인 상태를 반환합니다.
        -   `useAuthStore` (Zustand 스토어)가 `localStorage`를 직접 조작하는 대신, 위 API들을 `fetch`로 호출하도록 로직을 전면 수정했습니다.
    
    ### 2. 중앙 집중 제어: 미들웨어 도입 (`src/middleware.ts`)
    
    -   **문제점**: 로그인/로그아웃 상태에 따른 페이지 이동(리다이렉션) 로직이 `(authenticated)/layout.tsx`, `login/page.tsx`, `page.tsx` 등 여러 파일에 흩어져 있어 유지보수가 어려웠습니다.
    -   **리팩토링**:
        -   프로젝트 루트에 `middleware.ts` 파일을 생성하여 모든 접근 제어 로직을 통합했습니다.
        -   미들웨어는 사용자의 모든 페이지 요청을 가로채, 요청에 포함된 `auth-token` 쿠키의 유무를 확인합니다.
        -   인증 상태와 접근하려는 경로(공개/보호)를 비교하여, 서버단에서 직접 적절한 페이지로 리다이렉트 시킵니다.
        -   `matcher` 설정을 통해 API나 정적 파일 등 불필요한 경로에서는 미들웨어가 실행되지 않도록 최적화했습니다.
    
    ### 3. 컴포넌트 코드 단순화
    
    -   **문제점**: 여러 컴포넌트들이 인증 상태를 확인하고 페이지를 이동시키는 책임을 함께 가지고 있어 코드가 복잡했습니다.
    -   **리팩토링**:
        -   미들웨어가 모든 접근 제어를 담당하게 되면서, 각 페이지 컴포넌트에 있던 `useEffect` 기반의 리다이렉션 로직을 모두 제거했습니다.
        -   이를 통해 각 컴포넌트는 본래의 목적인 UI 렌더링에만 집중하게 되어, 코드가 훨씬 간결하고 명확해졌습니다.
    
    ## 🏁 리팩토링 결과 및 기대효과
    
    -   **유지보수성 향상**: 인증 관련 정책이 변경될 경우, 이제 `middleware.ts` 파일 하나만 수정하면 되므로 관리가 매우 용이해졌습니다.
    -   **구조적 개선**: 실제 프로덕션 환경에서 사용될 `httpOnly` 쿠키 기반 인증과 거의 동일한 아키텍처를 갖추게 되어, 향후 실제 백엔드 API와 연동 시 코드 변경을 최소화할 수 있습니다.
    -   **성능 및 안정성**: 클라이언트 측에서 여러 번 실행되던 리다이렉션 로직이 서버 요청 단계에서 한 번만 실행되므로, 불필요한 렌더링이나 화면 깜빡임 현상이 사라져 사용자 경험이 개선됩니다.
    
    ---
    
    ## 📝 아키텍처 변경에 따른 주요 컴포넌트 역할 재정의
    
    이번 리팩토링으로 인해 기존 컴포넌트들의 역할이 다음과 같이 명확하게 재정의되었습니다.
    
    -   **`middleware.ts`**: **페이지 접근 제어 담당**
        -   서버 요청 단계에서 쿠키를 검사하여, 허용되지 않은 페이지 접근을 막고 올바른 경로로 리다이렉트 시키는 **'경비원'** 역할을 전담합니다.
    
    -   **`useAuthStore` (Zustand 스토어)**: **클라이언트 UI 상태 관리 담당**
        -   페이지 이동 로직은 미들웨어에 위임하고, 이제 '로그인'/'로그아웃' 버튼 텍스트 변경이나 사용자 프로필 표시 등 **사용자에게 보여지는 UI를 제어하는 '상태 표시판'** 역할을 합니다.
        -   사용자가 '로그인' 버튼을 클릭했을 때, `/api/auth/login` API를 호출하는 액션을 트리거합니다.
    
    -   **`AuthInitializer.tsx`**: **서버-클라이언트 상태 동기화 담당**
        -   앱이 처음 로드될 때, `/api/auth/status` API를 호출하여 **서버의 실제 쿠키 상태를 클라이언트의 `useAuthStore` 상태와 일치시키는 '최초 동기화'** 작업을 수행합니다.
        -   이 과정을 통해 사용자가 로그인 상태임에도 불구하고 UI가 잠시 로그아웃 상태로 보이는 등의 깜빡임(flickering) 현상을 방지합니다. 
profile
무서운 속도로 흡수하는 스펀지 개발자 🧽

0개의 댓글