MMeshOps
AI가 놓치는 엣지 케이스까지: Next.js와 TypeScript 실전
12예상 10입문

접근 차단과 인증: 권한 없음, 그리고 인증 상태 관리

로그인 여부에 따라 화면을 다르게 보여주고, 권한이 없는 페이지 접근을 막아내는 프론트엔드 보호 기법을 배웁니다.

우리가 매일 사용하는 웹서비스에는 철저한 문지기가 존재합니다. 로그인하지 않은 사람은 '마이페이지'에 들어갈 수 없고, 다른 사람의 글을 수정할 수도 없어야 합니다. 이번 에피소드에서는 사용자가 누구인지 확인하는 인증(Authentication)과 권한이 없는 접근을 막아내는 권한 없음(Unauthorized) 상태를 프론트엔드에서 어떻게 처리하는지 다룹니다.

문을 잠그는 두 가지 방법: 인증과 인가

프론트엔드 개발을 하다 보면 '인증(Authentication)'과 '인가(Authorization)'라는 단어를 자주 마주칩니다. 비슷해 보이지만 명확히 다릅니다.

  • 인증(Authentication): "당신은 누구인가?"를 확인하는 과정입니다. (예: 아이디와 비밀번호로 로그인)
  • 인가(Authorization): "당신이 이 행동을 할 권한이 있는가?"를 확인하는 과정입니다. (예: 관리자만 공지사항 작성 가능)

프론트엔드 애플리케이션은 서버로부터 받은 토큰(Token)이나 세션(Session) 정보를 저장하고, 이 정보가 유효한지에 따라 화면을 다르게 그려주어야 합니다.

권한 없는 접근 방어하기 (401과 403)

사용자가 로그인을 하지 않고 비밀 페이지에 접근하거나, 일반 유저가 관리자 메뉴를 누르는 상황은 언제든 발생합니다. 서버는 이때 보통 두 가지 상태 코드 중 하나를 보냅니다.

  • 401 Unauthorized: 인증되지 않은 사용자입니다. (로그인하세요)
  • 403 Forbidden: 로그인했지만 해당 권한이 없습니다. (접근 금지)

우리는 서버가 이런 응답을 보냈을 때 앱이 멈추게 두지 않고, 사용자에게 적절한 안내 화면을 보여준 뒤 로그인 페이지로 친절하게 돌려보내야 합니다.

권한 오류 발생 시 프론트엔드의 대처

로그인 없이 마이페이지에 접속 (401)

로그인 모달을 띄우거나 로그인 페이지로 리다이렉트(Redirect) 합니다.

일반 유저가 관리자 페이지 접속 (403)

'접근 권한이 없습니다'라는 안내 화면과 함께 메인으로 이동시키는 버튼을 제공합니다.

로그인 세션 만료

사용자에게 '로그인이 만료되었습니다'라고 알린 후 자동으로 로그아웃 처리합니다.

화면을 조건부로 보여주기 (Conditional Rendering)

로그인 상태에 따라 상단 메뉴바(Header)의 모양이 달라지는 것을 자주 보셨을 것입니다. 로그아웃 상태일 때는 '로그인/회원가입' 버튼이, 로그인 상태일 때는 '프로필/로그아웃' 버튼이 보여야 합니다.

이러한 분기 처리를 리액트에서는 상태(State)와 조건문으로 간단히 해결합니다. 보통 앱 최상단에 인증 정보를 담아두고, 어느 컴포넌트에서든 그 정보를 꺼내 쓸 수 있도록 설계합니다.

사용자 인증 상태에 따른 메뉴 변경

export default function NavigationBar({ user }) {
  return (
    <nav className="flex justify-between p-4">
      <div className="logo">My App</div>
      
      {/* user 객체의 존재 여부에 따라 다르게 렌더링합니다 */}
      {user ? (
        <div className="user-menu">
          <span>{user.name}님 환영합니다!</span>
          <button>로그아웃</button>
        </div>
      ) : (
        <div className="login-menu">
          <button>로그인</button>
          <button>회원가입</button>
        </div>
      )}
    </nav>
  );
}

Next.js 미들웨어(Middleware)로 입구 봉쇄하기

React(클라이언트 사이드)에서만 권한을 처리하면, 사용자가 마이페이지에 접속했을 때 잠깐(찰나의 순간) 화면이 보였다가 로그인 페이지로 튕기는 '깜빡임' 현상이 발생할 수 있습니다.

Next.js는 이를 근본적으로 해결하기 위해 미들웨어(Middleware)라는 강력한 기능을 제공합니다. 미들웨어는 사용자의 요청이 페이지 컴포넌트에 닿기 직전에 중간에서 요청을 가로채어 검사합니다.

미들웨어를 활용한 라우팅 보호 규칙

  1. 1요청이 들어오면 미들웨어가 가장 먼저 실행되어 사용자의 토큰(쿠키)을 확인합니다.
  2. 2토큰이 없는데 보호된 경로(예: /dashboard)로 가려고 하면 강제로 로그인 페이지로 보냅니다(Redirect).
  3. 3이 과정이 서버 단에서 이루어지므로 클라이언트 화면이 깜빡이지 않고 보안이 강력해집니다.

Next.js 미들웨어로 보호된 라우팅 만들기

typescript
```typescript
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // 쿠키에서 로그인 토큰을 가져옵니다.
  const token = request.cookies.get('auth_token');
  
  // 사용자가 마이페이지로 이동하려는지 확인합니다.
  if (request.nextUrl.pathname.startsWith('/mypage')) {
    // 토큰이 없다면 로그인 페이지로 리다이렉트합니다.
    if (!token) {
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }
}
```
  1. 1루트 폴더에 middleware.ts 파일을 생성합니다.
  2. 2요청 헤더나 쿠키에서 인증 토큰을 확인합니다.
  3. 3보호된 경로로 접근 시 토큰이 없다면 NextResponse.redirect로 방향을 틉니다.

조용한 실패와 사용자 경험

접근을 차단할 때는 강압적인 경고 메시지보다 상황을 이해시키는 부드러운 메시지가 좋습니다. "접근 금지!"라는 붉은 글씨보다, "로그인해야 이용할 수 있는 서비스입니다. 로그인 화면으로 이동하시겠어요?"처럼 다음 행동을 제안하는 것이 훌륭한 UI/UX입니다. 권한 없음(Unauthorized) 상태를 사용자 여정의 장애물이 아닌, 자연스러운 가입 및 로그인 유도 과정으로 설계하세요.

개념 퀴즈

퀴즈 답을 맞춰야 학습 완료가 됩니다.

Next.js에서 권한이 없는 사용자가 보호된 페이지에 접근할 때, 화면 깜빡임 없이 서버에서 미리 로그인 페이지로 이동시키기 위해 사용하는 기능은 무엇인가요?

3줄 요약

  1. 1인증(Authentication)은 누구인지 확인하는 것이고, 인가(Authorization)는 권한이 있는지 확인하는 것입니다.
  2. 2프론트엔드는 사용자의 인증 상태에 따라 화면을 조건부로 다르게 렌더링해야 합니다.
  3. 3Next.js 미들웨어를 활용하면 화면 깜빡임 없이 완벽하게 권한 없는 라우트 접근을 막을 수 있습니다.