본문으로 건너뛰기

안동민 개발노트

본문 시작

Auth.js 설정

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

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

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

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

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

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

인증과 인가는 Auth.js를 경계로 분리한다

신원을 확인한 뒤 세션을 만들고, 실제 행동 허용은 서버 정책이 결정한다.

  1. 인증 Provider

    사용자의 신원을 확인한다.

  2. Auth.js 세션 생성

    읽기 경계를 한곳에 둔다.

  3. 인가 서버

    역할과 소유권으로 행동을 허용한다.


인증 흐름 이해

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

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

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

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

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

OAuth 결과는 callback을 거쳐 세션이 된다

외부 로그인 성공만으로 앱 로그인이 끝나지 않는다.

  1. OAuth Provider

    신원을 확인한다.

  2. callback 앱의 허용 URL

    돌아온다.

  3. session Auth.js

    로그인 상태를 만든다.

  4. auth() 서버

    현재 세션을 읽는다.


패키지와 환경 변수 설정

먼저 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을 별도로 등록합니다.

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

환경 변수와 callback URL은 한 계약이다

이름, 값, Provider 등록 주소가 모두 맞아야 OAuth가 연결된다.

  1. AUTH_SECRET 세션

    보호할 충분히 긴 비밀값

  2. AUTH_GITHUB_ID Provider

    발급한 앱 식별자

  3. AUTH_GITHUB_SECRET 서버에만 두

    Provider 비밀값

  4. Callback URL

    /api/auth/callback/github를 정확히 등록


중앙 인증 모듈 작성

프로젝트의 인증 설정은 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은 서버 액션에서 인증 흐름을 시작하고 끝냅니다.

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

auth.ts는 인증 기능의 단일 진입점이다

설정을 한 번 선언하고 서버와 route가 같은 함수를 공유한다.

  1. handlers Auth.js HTTP 요청

    처리한다.

  2. auth 서버

    현재 세션을 읽는다.

  3. signIn 로그인 흐름

    시작한다.

  4. signOut 현재 세션

    종료한다.


App Router 핸들러 연결

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

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

export const { GET, POST } = handlers;

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

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

catch-all route는 Auth.js handlers만 연결한다

Provider별 endpoint를 직접 만들지 않고 GET과 POST를 위임한다.

  1. 요청 /api/auth/* 경로

    들어온다.

  2. [...nextauth] 여러 인증 하위 경로

    한 route가 받는다.

  3. GET · POST handlers

    signin, callback, session을 처리한다.


서버에서 세션 읽기

서버 컴포넌트에서는 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의 존재를 검사합니다.

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

세션은 가능한 한 서버에서 읽는다

서버는 auth(), 상호작용이 필요한 클라이언트만 useSession을 쓴다.

  1. Server auth()

    세션을 읽고 렌더 전에 분기한다.

  2. Client SessionProvider 아래

    useSession으로 상태를 표시한다.


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

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

브라우저에서 세션 변화에 반응해야 하는 작은 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를 가져다 쓰는 구조가 되었습니다.

OAuth 연결은 네 경계를 순서대로 검증한다

추측하지 말고 설정에서 세션까지 좁혀 간다.

  1. 1
    환경 변수 AUTH_* 이름

    · 환경 변수 AUTH_* 이름과 배포 값을 확인한다.

  2. 2
    Provider 등록한 callback URL

    · Provider 등록한 callback URL을 확인한다.

  3. 3
    Route GET

    · Route GET과 POST handlers 연결을 확인한다.

  4. 4
    Session cookie

    · Session cookie와 auth() 결과를 확인한다.