본문으로 건너뛰기
안동민 개발노트 아이콘

안동민 개발노트

본문 시작
10장 : 인증 및 권한 관리

Auth.js 설정

Auth.js의 공급자·세션 구조를 이해하고 App Router에서 인증 핸들러와 서버 API를 설정합니다.

웹 애플리케이션의 인증(Authentication)은 사용자가 누구인지 확인하는 과정입니다.

권한 부여(Authorization)는 확인된 사용자가 어떤 기능을 사용할 수 있는지 판단하는 과정입니다.

Next.js에서는 Auth.js를 사용하면 OAuth 로그인, 세션 쿠키, 콜백과 라우트 핸들러를 직접 조립하는 부담을 줄일 수 있습니다.

이 장은 App Router와 현행 Auth.js API를 기준으로 진행합니다.

핵심은 설정 객체를 여러 파일에서 다시 꺼내 쓰지 않고, 한 번의 NextAuth() 호출에서 handlers, auth, signIn, signOut을 만들어 공유하는 것입니다.


인증 흐름 이해

사용자가 GitHub 로그인을 선택하면 브라우저는 GitHub의 인증 화면으로 이동합니다.

인증이 끝나면 GitHub가 애플리케이션의 콜백 URL로 사용자를 돌려보냅니다.

Auth.js는 콜백 요청을 검증하고 세션 쿠키를 만든 뒤 애플리케이션으로 이동시킵니다.

이후 서버 컴포넌트와 라우트 핸들러는 auth()로 현재 세션을 읽습니다.

클라이언트 컴포넌트는 꼭 필요한 경우에만 useSession()을 사용합니다.


패키지와 환경 변수 설정

먼저 Auth.js 패키지를 설치합니다.

npm install next-auth@beta

Auth.js v5가 안정 버전으로 설치되는 시점에는 프로젝트의 버전 정책에 맞춰 next-auth를 설치하면 됩니다.

프로젝트 루트의 .env.local에 인증 비밀 키와 GitHub OAuth 값을 저장합니다.

.env.local
AUTH_SECRET=충분히_긴_무작위_문자열
AUTH_GITHUB_ID=GitHub_OAuth_App의_Client_ID
AUTH_GITHUB_SECRET=GitHub_OAuth_App의_Client_Secret

비밀 키는 다음 명령으로 만들 수 있습니다.

npx auth secret

GitHub OAuth App의 개발 환경 콜백 URL은 다음과 같습니다.

http://localhost:3000/api/auth/callback/github

배포 환경에서는 실제 HTTPS 도메인으로 콜백 URL을 별도로 등록합니다.

환경 변수 파일은 저장소에 커밋하지 않습니다.


중앙 인증 모듈 작성

프로젝트의 인증 설정은 src/auth.ts 한곳에 둡니다.

src/auth.ts
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [GitHub],
  session: {
    strategy: 'jwt',
  },
});

GitHub 공급자는 AUTH_GITHUB_IDAUTH_GITHUB_SECRET을 자동으로 읽습니다.

handlers는 로그인·콜백·로그아웃 HTTP 요청을 처리합니다.

auth는 서버에서 현재 요청의 세션을 읽거나 요청을 보호합니다.

signInsignOut은 서버 액션에서 인증 흐름을 시작하고 끝냅니다.

설정 객체를 별도로 내보내거나 호출할 때마다 다시 전달하지 않습니다.


App Router 핸들러 연결

Auth.js가 만든 HTTP 핸들러를 catch-all 라우트에 연결합니다.

src/app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth';

export const { GET, POST } = handlers;

이 파일은 설정을 소유하지 않습니다.

URL 요청을 중앙 인증 모듈의 핸들러로 전달하는 역할만 합니다.


서버에서 세션 읽기

서버 컴포넌트에서는 auth()를 직접 호출합니다.

src/app/account/page.tsx
import { auth } from '@/auth';

export default async function AccountPage() {
  const session = await auth();

  if (!session?.user) {
    return <p>로그인이 필요합니다.</p>;
  }

  return <p>{session.user.email} 계정으로 로그인했습니다.</p>;
}

auth()는 서버 전용 API이므로 비밀 키와 세션 검증 로직이 브라우저 번들에 포함되지 않습니다.

로그인 여부만 확인할 때는 session?.user의 존재를 검사합니다.

사용자 식별자나 역할이 필요하다면 이후 콜백에서 세션 타입을 확장합니다.


클라이언트 세션이 필요한 경우

대부분의 페이지는 서버 컴포넌트에서 세션을 읽는 편이 단순합니다.

브라우저에서 세션 변화에 반응해야 하는 작은 UI에만 SessionProvideruseSession()을 사용합니다.

src/app/providers.tsx
'use client';

import { SessionProvider } from 'next-auth/react';

export function Providers({ children }: { children: React.ReactNode }) {
  return <SessionProvider>{children}</SessionProvider>;
}
src/app/layout.tsx
import { Providers } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Provider를 추가하면 루트 전체가 클라이언트 컴포넌트로 바뀌는 것은 아닙니다.

다만 클라이언트 세션이 전혀 필요하지 않다면 Provider도 추가하지 않습니다.


설정 확인

개발 서버를 실행한 뒤 /api/auth/signin에 접속합니다.

GitHub 로그인을 완료하고 애플리케이션으로 돌아오는지 확인합니다.

auth()를 호출한 서버 컴포넌트에서 사용자 정보가 보이는지 확인합니다.

환경 변수를 바꿨다면 개발 서버를 다시 시작합니다.

로그인 실패 시에는 공급자 키, 콜백 URL, AUTH_SECRET 순으로 점검합니다.

이제 인증 설정은 src/auth.ts에 모였고, 나머지 코드는 생성된 API를 가져다 쓰는 구조가 되었습니다.