icon
10장 : 인증 및 권한 관리

NextAuth.js 설정


웹 애플리케이션에서 사용자 인증(Authentication)권한 부여(Authorization) 는 핵심적인 기능입니다. 사용자의 신원을 확인하고, 해당 사용자가 특정 리소스나 기능에 접근할 권한이 있는지 관리하는 것은 보안과 사용자 경험 모두에 필수적입니다. Next.js 애플리케이션에서 이러한 복잡한 인증 과정을 효율적으로 처리하기 위해 NextAuth.js (현행 명칭 Auth.js) 라이브러리가 널리 사용됩니다.

NextAuth.js는 다양한 소셜 로그인(OAuth), 이메일/비밀번호 로그인, 데이터베이스 연동 등 광범위한 인증 전략을 지원하며, Next.js App Router와 완벽하게 통합됩니다. 이 절에서는 NextAuth.js의 기본 개념, 설치 방법, 그리고 Next.js App Router 환경에서 초기 설정을 하는 과정을 상세히 알아보겠습니다.


NextAuth.js (Auth.js)란 무엇인가요?

NextAuth.js (Auth.js)는 Next.js 애플리케이션을 위한 오픈 소스 인증 솔루션입니다. 다음과 같은 주요 특징을 가집니다.

  • 쉬운 통합: Next.js App Router와 Page Router 모두에서 쉽게 통합될 수 있도록 설계되었습니다.
  • 다양한 Provider 지원: Google, GitHub, Kakao, Naver 등 수많은 OAuth Provider (소셜 로그인), 이메일/비밀번호 없는 로그인(Magic Link), 자격 증명(Credentials) 로그인 등을 기본으로 지원합니다.
  • 세션 관리: 사용자 세션을 쿠키 또는 데이터베이스를 통해 안전하게 관리합니다.
  • JWT (JSON Web Tokens) 지원: 세션 정보를 JWT 형태로 암호화하여 클라이언트와 서버 간에 안전하게 전달할 수 있습니다.
  • 데이터베이스 어댑터: TypeORM, Prisma, Drizzle 등 다양한 ORM/데이터베이스와 연동하여 사용자 및 세션 정보를 영구적으로 저장할 수 있습니다.
  • 보안: CSRF 보호, 토큰 암호화, 역할 기반 접근 제어 등의 보안 기능을 내장하고 있습니다.
  • 서버 컴포넌트와의 호환성: Next.js App Router의 서버 컴포넌트 환경에서도 세션 정보를 안전하게 가져올 수 있습니다.

NextAuth.js 설정 단계

NextAuth.js를 Next.js App Router 프로젝트에 설정하는 과정은 크게 다음과 같은 단계로 이루어집니다.

  1. NextAuth.js 패키지 설치
  2. 환경 변수 설정
  3. Auth.js 핸들러 라우트 정의
  4. 세션 Provider 설정 (클라이언트)
  5. 인증 상태 사용 (클라이언트/서버)

NextAuth.js 패키지 설치

먼저, 프로젝트에 NextAuth.js (Auth.js) 패키지를 설치합니다.

npm install next-auth
# 또는
yarn add next-auth

환경 변수 설정 (.env.local)

NextAuth.js는 보안을 위해 여러 환경 변수를 필요로 합니다. 프로젝트 루트에 .env.local 파일을 생성하고 다음 변수들을 추가합니다.

# .env.local

# NextAuth.js의 secret 키. 보안을 위해 길고 무작위적인 문자열을 사용해야 합니다.
# 터미널에서 `openssl rand -base64 32` 또는 `npx auth secret` 명령어로 생성할 수 있습니다.
NEXTAUTH_SECRET=YOUR_VERY_LONG_RANDOM_SECRET_KEY

# 애플리케이션의 URL (배포 시 필수)
NEXTAUTH_URL=http://localhost:3000

# GitHub OAuth Provider 설정 예시
# GitHub Developers 페이지에서 OAuth App을 생성하여 Client ID와 Secret을 얻습니다.
# Callback URL: http://localhost:3000/api/auth/callback/github
GITHUB_ID=YOUR_GITHUB_CLIENT_ID
GITHUB_SECRET=YOUR_GITHUB_CLIENT_SECRET

NEXTAUTH_SECRET 생성 방법

  • 추천: 터미널에서 다음 명령어 중 하나를 실행하여 안전한 비밀 키를 생성합니다.
    openssl rand -base64 32 # macOS/Linux
    # 또는
    npx auth secret # Auth.js CLI (auth 패키지 설치 필요: npm i auth)
  • 생성된 문자열을 .env.local 파일의 NEXTAUTH_SECRET 값으로 복사합니다.

OAuth Provider 설정 (예: GitHub)

  • 각 소셜 로그인 Provider는 별도의 IDSECRET을 요구합니다.
  • 예시로 GitHub를 들었지만, Google, Kakao, Naver 등 원하는 Provider의 개발자 콘솔에서 OAuth 애플리케이션을 등록하고 Client IDClient Secret을 발급받아야 합니다.
  • Provider 설정 시 Callback URL 또는 Redirect URINEXTAUTH_URL/api/auth/callback/[provider-name] 형식으로 설정해야 합니다. (예: GitHub는 http://localhost:3000/api/auth/callback/github)

Auth.js 핸들러 라우트 정의

NextAuth.js는 모든 인증 요청을 처리하는 동적 라우트 핸들러가 필요합니다. App Router에서는 src/app/api/auth/[...nextauth]/route.ts (또는 .js) 경로에 이 핸들러를 정의합니다.

src/app/api/auth/[...nextauth]/route.ts
// src/app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import GitHubProvider from 'next-auth/providers/github';
// import GoogleProvider from 'next-auth/providers/google';
// import KakaoProvider from 'next-auth/providers/kakao';
// import NaverProvider from 'next-auth/providers/naver';

// NextAuth 설정을 정의하는 객체
const authOptions = {
  // 1. 인증 Provider 설정
  providers: [
    GitHubProvider({
      clientId: process.env.GITHUB_ID as string, // 환경 변수에서 GitHub Client ID 가져오기
      clientSecret: process.env.GITHUB_SECRET as string, // 환경 변수에서 GitHub Client Secret 가져오기
    }),
    // 다른 Provider도 필요에 따라 추가할 수 있습니다.
    // GoogleProvider({
    //   clientId: process.env.GOOGLE_ID as string,
    //   clientSecret: process.env.GOOGLE_SECRET as string,
    // }),
    // KakaoProvider({
    //   clientId: process.env.KAKAO_ID as string,
    //   clientSecret: process.env.KAKAO_SECRET as string,
    // }),
    // NaverProvider({
    //   clientId: process.env.NAVER_ID as string,
    //   clientSecret: process.env.NAVER_SECRET as string,
    // }),
  ],

  // 2. JWT 및 세션 설정
  session: {
    strategy: 'jwt', // 세션 관리를 JWT (JSON Web Token) 방식으로 설정
    // JWT 세션의 만료 시간 (기본값은 30일)
    // maxAge: 30 * 24 * 60 * 60, // 30 days
  },

  // 3. 콜백 설정 (선택 사항)
  // JWT가 생성되거나 세션이 업데이트될 때 추가적인 로직을 수행할 수 있습니다.
  callbacks: {
    async jwt({ token, user, account }) {
      // 초기 로그인 시 (user 객체가 존재할 때)
      if (account && user) {
        token.accessToken = account.access_token; // OAuth access token 저장
        token.id = user.id; // 사용자 ID 저장 (DB 연동 시 필요)
      }
      return token;
    },
    async session({ session, token }) {
      // 세션에 추가 정보를 포함
      session.accessToken = token.accessToken as string;
      session.user.id = token.id as string; // 세션 user 객체에 id 추가
      return session;
    },
  },

  // 4. 페이지 설정 (선택 사항)
  // 로그인, 로그아웃, 에러 페이지 등을 커스터마이징할 수 있습니다.
  pages: {
    // signIn: '/auth/signin', // 커스텀 로그인 페이지 경로
    // signOut: '/auth/signout',
    // error: '/auth/error',
  },

  // 5. 시크릿 키 설정
  secret: process.env.NEXTAUTH_SECRET,
};

// GET 및 POST 요청을 처리하기 위한 핸들러
const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

주의: process.env.NEXTAUTH_SECRET과 각 PROVIDER_ID, PROVIDER_SECRET은 빌드 시점에 환경 변수 값이 주입되므로, 타입스크립트 에러를 방지하기 위해 as string을 붙여줍니다.

세션 Provider 설정 (클라이언트 컴포넌트)

애플리케이션의 클라이언트 측에서 세션에 접근하려면 SessionProvider로 애플리케이션을 감싸야 합니다. 이는 Next.js의 Root Layout (src/app/layout.tsx)에서 설정하는 것이 일반적입니다.

src/app/layout.tsx
// src/app/layout.tsx
import './globals.css';
import { SessionProvider } from 'next-auth/react'; // NextAuth.js의 SessionProvider 임포트

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ko">
      <body>
        {/* SessionProvider로 자식 컴포넌트들을 감쌉니다. */}
        {/* 이 Provider는 클라이언트 컴포넌트이므로, use client 지시어를 가진 별도의 컴포넌트로 분리하는 것이 좋습니다. */}
        <SessionProvider>
          {children}
        </SessionProvider>
      </body>
    </html>
  );
}

SessionProvider 분리 (Best Practice)

SessionProvider는 클라이언트 컴포넌트여야 하므로, 서버 컴포넌트인 layout.tsx에 직접 포함하기보다 별도의 클라이언트 컴포넌트로 분리하여 임포트하는 것이 Next.js의 모범 사례입니다.

  1. src/app/providers.tsx 파일 생성 (클라이언트 컴포넌트)

    src/app/providers.tsx
    // src/app/providers.tsx
    "use client"; // 클라이언트 컴포넌트임을 명시
    
    import { SessionProvider } from 'next-auth/react';
    import { ReactNode } from 'react';
    
    interface ProvidersProps {
      children: ReactNode;
    }
    
    export default function Providers({ children }: ProvidersProps) {
      return (
        <SessionProvider>
          {children}
        </SessionProvider>
      );
    }
  2. src/app/layout.tsx 수정

    src/app/layout.tsx
    // src/app/layout.tsx
    import './globals.css';
    import Providers from './providers'; // 분리된 Providers 컴포넌트 임포트
    
    export default function RootLayout({
      children,
    }: {
      children: React.ReactNode;
    }) {
      return (
        <html lang="ko">
          <body>
            <Providers> {/* Providers로 감싸기 */}
              {children}
            </Providers>
          </body>
        </html>
      );
    }

인증 상태 사용하기

이제 NextAuth.js를 사용하여 사용자 인증 상태를 가져오고 UI를 업데이트할 수 있습니다.

  • 클라이언트 컴포넌트에서: useSession 훅을 사용합니다.
  • 서버 컴포넌트에서: getServerSession 함수를 사용합니다.

실습: 로그인/로그아웃 버튼 및 사용자 정보 표시

  1. src/app/auth/page.tsx 파일 생성 (서버 컴포넌트): 서버에서 사용자 세션을 가져와 클라이언트 컴포넌트에 전달하고, 로그인/로그아웃 버튼을 렌더링합니다.

    src/app/auth/page.tsx
    // src/app/auth/page.tsx
    import { getServerSession } from 'next-auth'; // 서버에서 세션 가져오기
    import { authOptions } from '../api/auth/[...nextauth]/route'; // 인증 옵션 임포트
    import UserInfo from './UserInfo'; // 클라이언트 컴포넌트 임포트
    import AuthButtons from './AuthButtons'; // 클라이언트 컴포넌트 임포트
    
    export default async function AuthPage() {
      // 서버 컴포넌트에서 세션 정보 가져오기
      const session = await getServerSession(authOptions);
      console.log('Server Session:', session); // 서버 콘솔에서 세션 확인
    
      return (
        <div style={{ padding: '20px', maxWidth: '600px', margin: '20px auto', border: '1px solid #007bff', borderRadius: '10px', boxShadow: '0 4px 8px rgba(0,0,0,0.1)', textAlign: 'center' }}>
          <h1 style={{ color: '#007bff', marginBottom: '20px' }}>인증 및 권한 관리 (NextAuth.js)</h1>
    
          <h2 style={{ color: '#333', marginBottom: '15px' }}>사용자 정보</h2>
          <UserInfo /> {/* 클라이언트 컴포넌트에서 세션 사용 */}
    
          <hr style={{ margin: '30px 0', borderColor: '#eee' }} />
    
          <h2 style={{ color: '#333', marginBottom: '15px' }}>인증 액션</h2>
          <AuthButtons /> {/* 클라이언트 컴포넌트에서 로그인/로그아웃 액션 */}
        </div>
      );
    }
  2. src/app/auth/UserInfo.tsx 파일 생성 (클라이언트 컴포넌트): useSession 훅을 사용하여 클라이언트에서 사용자 정보를 표시합니다.

    src/app/auth/UserInfo.tsx
    // src/app/auth/UserInfo.tsx
    "use client";
    
    import { useSession } from 'next-auth/react'; // 클라이언트에서 세션 가져오기
    
    export default function UserInfo() {
      const { data: session, status } = useSession(); // useSession 훅 사용
    
      if (status === 'loading') {
        return <p>로딩 중...</p>;
      }
    
      if (session) {
        return (
          <div style={{ padding: '15px', backgroundColor: '#e6ffe6', borderRadius: '8px', border: '1px solid #b3ffb3' }}>
            <p style={{ fontWeight: 'bold', color: '#28a745', marginBottom: '10px' }}>로그인됨!</p>
            <p>이름: {session.user?.name}</p>
            <p>이메일: {session.user?.email}</p>
            {session.user?.image && (
              <img src={session.user.image} alt="User Avatar" style={{ width: '50px', height: '50px', borderRadius: '50%', marginTop: '10px' }} />
            )}
            <p style={{ marginTop: '10px', fontSize: '0.9em', color: '#555' }}>
              (이 정보는 클라이언트 컴포넌트에서 `useSession` 훅으로 가져왔습니다.)
            </p>
          </div>
        );
      }
    
      return (
        <p style={{ padding: '15px', backgroundColor: '#ffe6e6', borderRadius: '8px', border: '1px solid #ffb3b3', color: '#dc3545' }}>
          로그아웃되었습니다.
          <p style={{ fontSize: '0.9em', color: '#555', marginTop: '5px' }}>
            (클라이언트 컴포넌트에서 세션이 없습니다.)
          </p>
        </p>
      );
    }
  3. src/app/auth/AuthButtons.tsx 파일 생성 (클라이언트 컴포넌트): signInsignOut 함수를 사용하여 인증 플로우를 시작하고 종료합니다.

    src/app/auth/AuthButtons.tsx
    // src/app/auth/AuthButtons.tsx
    "use client";
    
    import { useSession, signIn, signOut } from 'next-auth/react'; // signIn, signOut 함수 임포트
    
    export default function AuthButtons() {
      const { data: session } = useSession();
    
      if (session) {
        return (
          <button
            onClick={() => signOut({ callbackUrl: '/' })} // 로그아웃 후 리다이렉트할 경로 설정
            style={{
              padding: '12px 25px',
              backgroundColor: '#dc3545',
              color: 'white',
              border: 'none',
              borderRadius: '8px',
              cursor: 'pointer',
              fontSize: '1.1em',
              transition: 'background-color 0.3s ease'
            }}
          >
            로그아웃
          </button>
        );
      }
    
      return (
        <button
          onClick={() => signIn('github', { callbackUrl: '/auth' })} // GitHub 로그인, 로그인 후 현재 페이지로 리다이렉트
          style={{
            padding: '12px 25px',
            backgroundColor: '#007bff',
            color: 'white',
            border: 'none',
            borderRadius: '8px',
            cursor: 'pointer',
            fontSize: '1.1em',
            transition: 'background-color 0.3s ease'
          }}
        >
          GitHub로 로그인
        </button>
      );
    }

실습 확인

  1. NextAuth.js 패키지 설치를 완료했는지 확인합니다.
  2. .env.local 파일에 NEXTAUTH_SECRET, NEXTAUTH_URL, 그리고 GITHUB_ID, GITHUB_SECRET을 올바르게 설정했는지 확인합니다. (GitHub OAuth App 등록 필요)
  3. src/app/api/auth/[...nextauth]/route.ts 파일을 생성/수정합니다.
  4. src/app/providers.tsx 파일을 생성하고 src/app/layout.tsxProviders를 적용합니다.
  5. src/app/auth 폴더를 만들고 page.tsx, UserInfo.tsx, AuthButtons.tsx 파일을 생성합니다.
  6. 개발 서버(npm run dev)를 실행한 후, http://localhost:3000/auth로 접속합니다.
  7. "GitHub로 로그인" 버튼을 클릭하여 GitHub 로그인 페이지로 이동합니다. 로그인 과정을 완료하면 애플리케이션으로 돌아와 사용자 정보가 표시되는 것을 볼 수 있습니다.
  8. "로그아웃" 버튼을 클릭하여 세션을 종료합니다.

다음 단계: 데이터베이스 연동 및 권한 관리

이 절에서는 NextAuth.js의 기본 설정과 OAuth Provider를 사용한 인증 흐름을 살펴보았습니다. 실제 프로덕션 환경에서는 사용자 정보를 데이터베이스에 영구적으로 저장하고, 사용자 역할(예: admin, editor, guest)에 따라 접근 권한을 관리해야 합니다.

  • 데이터베이스 어댑터: next-auth/prisma-adapter, next-auth/typeorm-adapter 등 다양한 어댑터를 설치하고 authOptions에 추가하여 데이터베이스와 연동합니다.
  • 권한 관리: 세션 정보에 사용자 역할을 추가하고, 서버 컴포넌트에서 getServerSession으로 사용자 역할을 확인하거나, 클라이언트 컴포넌트에서 useSession으로 역할을 확인하여 UI 또는 API 접근을 제어합니다. 미들웨어나 커스텀 API 라우트를 통해 서버 측에서 강력한 권한 검사를 구현할 수 있습니다.

NextAuth.js는 Next.js 애플리케이션에 강력하고 유연하며 안전한 인증 시스템을 구축하는 데 필수적인 도구입니다.