SSR에서 유저 권한 라우팅 처리

질문Bot·2025년 6월 23일

Next.js

목록 보기
7/13
post-thumbnail

🙃 시작하기 앞서

제가 작성한 middleware를 통해 데스크톱, 모바일 분리

이전에 진행했던 프로젝트에서는 middleware를 활용해서 모바일과 데스크톱을 분리한 경험이 있습니다. 그 경험을 통해 Next.js에서의 서버 기반 라우팅 처리에 익숙해졌고, 사용자 디바이스에 따라 리소스를 나눠 쓰며 성능도 개선할 수 있었죠.

이번에는 그 연장선에서, 유저의 권한(Role)에 따라 접근 가능한 경로를 분기하는 작업을 진행해봤습니다.


🍪 왜 middleware로 유저 권한을 선택했나?

보통 Client-Side Rendering 기반의 React 프로젝트에서는 router.tsx 혹은 useEffect 안에서 localStorage에 저장된 토큰을 확인해서 라우팅을 제어하곤 하죠.

하지만 Next.js는 Server-Side Rendering을 기본으로 사용하는 프레임워크입니다.
여기서 중요한 포인트가 하나 있습니다.

❌ 서버 사이드에서는 localStorage를 쓸 수 없습니다.
✅ 서버는 클라이언트의 쿠키(cookie)만 읽을 수 있습니다.

즉, SSR 환경에서는 유저 권한이나 인증 정보를 localStorage가 아니라 쿠키에 저장해야만 미들웨어나 getServerSideProps 같은 서버 측 로직에서 접근할 수 있습니다.

그래서 저는 이번에 권한 분기를 처리할 때, 다음과 같은 진행을 하였습니다.

  • 유저의 token과 userRole 정보를 쿠키에 저장하고,
  • src/middleware.ts 파일을 통해
  • ADMIN 권한이 아닌 유저는 /admin 경로에 접근하지 못하도록 서버에서 바로 차단하는 방식으로 처리했습니다.

📁 src/middleware.ts

import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'

export function middleware(request: NextRequest) {
	const token = request.cookies.get('token')?.value
	const userRole = request.cookies.get('userRole')?.value

	// Admin 관리
	if (!(token && userRole === 'ADMIN')) {
		// 토큰 및 userRole 검사
		return NextResponse.redirect(new URL('/', request.url))
	}

	// 요청 계속 진행
	return NextResponse.next()
}

export const config = {
	matcher: ['/admin((?!/login|/_next|/api|/favicon.ico).*)'],
}

🍀 middleware 장점

이 방식의 가장 큰 장점은 페이지가 렌더링되기도 전에 서버에서 권한을 검사할 수 있다는 점입니다.

❌ 불필요한 페이지 깜빡임 방지 + 불필요한 리소스 차단

🔁 CSR 방식: useEffect를 통한 검사

useEffect(() => {
  const token = localStorage.getItem('token')
  if (!token) {
    router.push('/') // 로그인 안 되어 있으면 리디렉션
  }
}, [])

CSR 방식 흐름

  1. 먼저 /admin 페이지가 렌더링
  2. 그 다음에 useEffect()가 실행
  3. 조건에 따라 리디렉션되는 구조예요.

이 방식의 문제는 관리자 페이지가 잠깐 보였다가 바로 홈으로 튕기는 "Flickering" 현상이 발생한다는 점입니다.
또한 이 과정에서 /admin 페이지의 JS 번들, 초기 리소스가 이미 다운로드된 이후이기 때문에 네트워크 낭비도 발생합니다.

✅ SSR 방식: middleware를 통한 선제적 검사

SSR + middleware 방식 흐름

  1. 사용자가 /admin 경로로 접근 시도하면
  2. 서버는 쿠키에 담긴 token, userRole을 즉시 확인하고
  3. 조건에 부합하지 않으면 페이지를 렌더링하지 않고 곧바로 홈으로 리다이렉트합니다.

즉, 클라이언트는 /admin 페이지를 아예 다운로드하지도, 렌더링하지도 않습니다.

  • 깜빡임 없는 자연스러운 UX 제공
  • 불필요한 JS 번들 및 데이터 로딩 차단
    => 결과적으로 리소스 최적화로 이어집니다.

😑 middleware만으로는 부족한 이유

middleware.ts를 통해 쿠키에 담긴 token의 존재 여부와 userRole 값을 기반으로 1차 필터링을 수행할 수 있습니다.

하지만 이 방식은 쿠키가 클라이언트에 의해 조작될 수 있다는 점에서 완전히 안전하다고 보기 어렵습니다.

예를 들어, 개발자 도구를 통해 사용자가 userRole=ADMIN 같은 값을 임의로 삽입할 수도 있습니다.

그렇다면 이런 상황에서 어떻게 방어해야 할까요?


➕ 2차 검증 (서버 측 검증)

저는 이를 보완하기 위해, 서버 측 렌더링 단계에서 백엔드 API를 호출하여 토큰의 실제 유효성을 확인하는 "2차 검증"을 함께 적용했습니다

이를 통해 다음과 같은 장점을 얻을 수 있습니다:

  • ✅ 클라이언트에서 조작된 쿠키 무효화
  • ✅ 백엔드 DB 또는 JWT 서명을 기반으로 한 신뢰도 높은 인증
  • ✅ 세션 만료, 권한 변경 등 실시간 상태 반영
export async function getServerSideProps(context) {
  const token = context.req.cookies.token

  const res = await fetch('백엔드 API', {
    headers: { Authorization: `Bearer ${token}` }
  })

  if (res.status !== 200) {
    return {
      redirect: {
        destination: '/',
        permanent: false,
      },
    }
  }

  return { props: {} }
}

💡 결론

  • middleware.ts는 빠르고 가벼운 선 필터링
  • getServerSideProps를 추가해서 신뢰성 있는 서버 검증
profile
유용한 정보를 전달하는 사람이 되고자 노력합니다.

0개의 댓글