Next.js 교재 · 19편 · 인증

인증 패턴 — Auth.js (NextAuth) v5

직접 만들지 말 것. 보안 사고 1위가 자작 인증. 검증된 라이브러리로.

"로그인 직접 만들면 30분이면 되지 않나?" — 표면은 그렇다. 비밀번호 해시, 세션 토큰 발급, 만료 갱신, OAuth 제공자 토큰 검증, CSRF 방어, 안전한 쿠키, 비밀번호 재설정 이메일, 계정 잠금… 곧 30시간이 된다. 그리고 보안 사고는 그 사이 어딘가에서 터진다.

Next.js 진영의 답은 Auth.js (옛 이름 NextAuth.js). v5 부터 App Router 와 완벽 통합. Google·GitHub·이메일·자체 DB 어떤 방식이든 한 설정 파일로 끝. 이번 편이 그 첫 세팅.

1. 설치와 환경변수

$ npm install next-auth@beta # v5 (2026 기준 beta·rc 가 표준)

.env.local:

AUTH_SECRET=long-random-string-at-least-32-chars AUTH_GOOGLE_ID=... AUTH_GOOGLE_SECRET=... AUTH_GITHUB_ID=... AUTH_GITHUB_SECRET=... AUTH_URL=http://localhost:3000 # production 은 실제 도메인

AUTH_SECRET 생성 — openssl rand -base64 32 또는 npx auth secret. 절대 깃에 넣지 말 것.

OAuth 키 발급 — Google: Cloud Console > Credentials > OAuth 2.0 client. GitHub: Settings > Developer settings > OAuth Apps. 콜백 URL 은 {AUTH_URL}/api/auth/callback/{provider} 형식.

2. auth.ts 설정 — 한 파일로 끝

// auth.ts (프로젝트 루트) import NextAuth from 'next-auth'; import Google from 'next-auth/providers/google'; import GitHub from 'next-auth/providers/github'; export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [Google, GitHub], pages: { signIn: '/login', // 커스텀 로그인 페이지 }, callbacks: { async session({ session, token }) { // 세션에 추가 정보 박기 if (session.user) session.user.id = token.sub; return session; }, }, });

이 한 파일이 export 하는 4가지를 어디서나 import 해서 쓴다.

export용도
handlersAPI Route 에 그대로 연결
auth()서버 어디서나 현재 세션 읽기
signIn(provider)Server Action 에서 로그인 트리거
signOut()Server Action 에서 로그아웃

3. API Route 연결

// app/api/auth/[...nextauth]/route.ts import { handlers } from '@/auth'; export const { GET, POST } = handlers;

이 한 줄이 /api/auth/signin·/api/auth/callback/google·/api/auth/session 등 십수 개 엔드포인트를 자동 생성. Auth.js 가 OAuth flow·세션 발급·콜백 처리를 다 알아서.

4. 세션 읽기 — 서버·클라이언트

서버 컴포넌트·Server Action·Route Handler

// app/dashboard/page.tsx import { auth } from '@/auth'; import { redirect } from 'next/navigation'; export default async function DashboardPage() { const session = await auth(); if (!session) redirect('/login'); return <h1>안녕, {session.user.name}</h1>; }

클라이언트 컴포넌트

'use client'; import { useSession, signIn, signOut } from 'next-auth/react'; export function AuthButtons() { const { data: session, status } = useSession(); if (status === 'loading') return <p>…</p>; if (session) return ( <> <span>{session.user.email}</span> <button onClick={() => signOut()}>로그아웃</button> </> ); return <button onClick={() => signIn('google')}>구글 로그인</button>; }

클라이언트에서 useSession() 을 쓰려면 app/layout.tsx 의 children 을 <SessionProvider> 로 감싸야. v5 는 더 매끄러운 패턴이 있지만 클라이언트 hook 자체는 같다.

5. middleware 로 페이지 가드

13편 미들웨어 응용 — /dashboard/* 전체를 인증 가드.

// middleware.ts import { auth } from '@/auth'; export default auth((req) => { const isProtected = req.nextUrl.pathname.startsWith('/dashboard'); if (isProtected && !req.auth) { const url = req.nextUrl.clone(); url.pathname = '/login'; url.searchParams.set('callbackUrl', req.nextUrl.pathname); return Response.redirect(url); } }); export const config = { matcher: ['/dashboard/:path*', '/api/admin/:path*'], };

Auth.js 의 auth() 함수가 미들웨어 형태로도 동작. req.auth 가 세션 객체 — 없으면 로그인으로 보냄.

매번 깜빡하는 것AUTH_SECRET 을 production 에 누락. 그 순간 모든 세션 토큰이 잘못된 서명이 되어 로그인이 깨진다. Vercel·호스팅 UI 에서 환경변수 등록 후 재배포 필수. 18편 환경변수의 Zod 검증으로 시작 시 잡는 게 가장 빠른 예방.

요약 — 19편 좌표

여기까지 정리. Auth.js v5auth.ts 한 파일에 provider 설정 → app/api/auth/[...nextauth]/route.ts 한 줄로 엔드포인트 자동 생성. 서버 어디서나 auth(), 클라이언트는 useSession(). 미들웨어로 /dashboard/* 가드. 직접 만들지 말 것 — 보안 사고 1위 경로. 다음 편에서 Vercel 배포로 한 클릭 라이브.

다음 편 예고 — Vercel 배포

GitHub 연동·도메인·환경변수·preview. 20편.

© 2026 주나이테크(주) @JUNAITECH