NextAuth.js는 Next.js 애플리케이션에서 간편하게 사용자 인증을 구현할 수 있도록 도와주는 라이브러리로, OAuth, 이메일, 자격 증명 기반 로그인을 쉽게 설정할 수 있습니다.
Next.js의 API Routes를 활용해서 서버측에서 인증을 처리하며, 로그인한 사용자의 세션 정보를 보통 JWT, 필요에 따라 데이터베이스에 저장하여 관리합니다. API Routes를 활용하기 때문에 서버리스 환경에서도 적용할 수 있습니다.
복잡한 인증로직을 직접 구현할 필요 없이, 보안성과 확장성을 갖춘 인증 시스템을 손쉽게 적용할 수 있습니다.
세션 | 토큰 | |
---|---|---|
개념 | 로그인 상태를 유지하는 방법 | 로그인 상태를 인증하는 증명서 |
활용 | 사용자가 로그인하면 로그인했다는 정보를 저장하기 때문에 사용자는 계속 로그인 상태를 유지하면서 다른 페이지로 이동할 수 있음 | 서버가 로그인했다고 기억하는게 아니라, 클라이언트에게 JWT 토큰이라고 불리는 로그인 증명서를 줌 |
놀이공원 자유이용권 상황에 비유
세션 방식에서는 놀이공원이 방문객의 입장 정보를 직접 관리하며, 이용할 때마다 내부 시스템을 통해 확인하고, 토큰 방식에서는 방문객이 입장 증명서인 팔찌를 소지하여 놀이공원 측은 이용할 때마다 팔찌의 유효성만 확인합니다.
세션은 관리 및 확인 주체가 놀이공원 측이고, 토큰은 방문객이라고 볼 수 있습니다.
기능 상황 세션 토큰 인증 놀이공원 입구에서 표를 구매해서 본인확인을 한다 인증 성공 후 놀이공원에 입장한다 놀이공원 측은 방문했다는 정보를 내부 시스템에 저장한다 놀이공원 측은 입장 확인을 마친 방문객에게 입장팔찌를 제공한다 요청때마다 확인 놀이기구에 이용할 때마다 직원이 내부 시스템을 조회해서 해당 방문객의 입장 여부를 확인한다 직원은 방문객이 착용한 팔찌를 확인하여 입장 여부를 판단한다 정보삭제 놀이공원을 떠날 때 놀이공원 측은 내부 시스템에서 해당 방문객의 정보를 삭제한다 방문객은 팔찌를 반납하거나 폐기한다
웹 애플리케이션에서 인증은 필수적인 기능이지만, 직접 구현하려면 많은 시간과 노력이 필요합니다.
Next.js는 강력한 풀스택 프레임워크이지만, 자체적으로 인증 기능을 제공하지 않기 때문에 직접 구현하거나 라이브러리를 사용할 수 있습니다.
OAuth 및 JWT 기반의 보안 로직을 직접 구현하는 것은 많은 시간이 필요하고, 복잡하며 유지보수가 어렵습니다.
Firebase Authentication, Auth0, Supabase 타 인증 서비스는 비용이 비쌀 수 있다는 단점과 함께 다음과 같은 커스텀 제한이 있습니다.
무료 오픈 소스이고, Next.js와 완벽하게 연동할 수 있습니다.
또한 OAuth, JWT, 이메일 로그인 등 다양한 인증 방식을 지원하며, 서버리스 환경에서 최적화된 인증을 제공합니다.
NextAuth.js는 Next.js의 API Routes를 활용해서 서버에서 인증을 처리합니다.
$ npm i next-auth
src/app/api/auth/[…nextauth]/route.ts
파일을 생성해서 /api/auth
엔드포인트를 설정하면, NextAuth에서 필요한 다음과 같은 API 엔드포인트는 자동으로 생성합니다.
/api/auth/signin
/api/auth/signout
/api/auth/session
/api/auth/callback/google
/api/auth/error
클라이언트에서 signIn()
을 호출하면 내부적으로 /api/auth/signin
에 네트워크 요청을 보고, 해당 엔드포인트에선 로그인 검증을 수행하고 세션을 생성합니다.
이때 로그인이 성공하면 JWT 토큰을 쿠키에 저장해서, 이후 요청에서 인증할 수 있도록 합니다.
로그인 이후에 클라이언트에서 useSession()
훅을 호출하면 NextAuth 내부적으로 /api/auth/session
에 네트워크 요청을 보내서 현재 로그인 상태를 확인합니다.
Next.js 13 이상에서는 App Router를 사용하며, 12 이하에서는 Pages Router를 사용합니다.
App Router와 Page Router는 서버 동작 방식이 다르기 때문에 API Route 설정 또한 이에 맞게 달라집니다.
export default NextAuth({})
형식을 사용해서 단일 핸들러로 노출합니다.
GET, POST 요청을 모두 자동으로 처리합니다.
// src/pages/api/auth/[…nextauth].ts
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { NextAuthOptions } from 'next-auth';
const authOptions: NextAuthOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
callbacks: {
async session({ session, token }) {
session.user.id = token.sub;
return session;
}
},
};
const handler = NextAuth(authOptions);
export default handler;
export default
를 사용하지 않고, export { handler as GET, handler as POST }
형식으로 API 핸들러를 분리하여 명시적으로 설정합니다.
// src/app/api/auth/[…nextauth]/route.ts
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { NextAuthOptions } from 'next-auth';
export const authOptions: NextAuthOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
async session({ session, token }) {
session.user.id = token.sub;
return session;
},
},
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
// .env.local
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
NEXTAUTH_URL=http://localhost:3000
Client ID & Secret 은 애플리케이션 단위로 발급되는 정보로, OAuth 인증을 사용할 때 서비스 제공자와 애플리케이션 간의 식별 및 보안 인증에 사용됩니다.
NEXTAUTH_URL 은 NextAuth.js 가 OAuth 인증한 뒤 리디렉션할 URL을 결정할 때 사용됩니다.
NEXTAUTH_URL 을 설정하지 않아도 NextAuth.js가 환경에 따라 자동으로 감지하겠지만, Vercel 등의 클라우드 환경에서는 정확한 URL을 명시하는 것이 안정적입니다.
NextAuth는 기본적으로 회원가입 기능을 제공하지 않습니다.
그래서 사용자는 NextAuth를 이용해서 로그인은 할 수 있지만, 새로운 계정을 만드는 기능은 개발자가 직접 만들어야 합니다.
하지만, 소셜로그인을 사용하면, 로그인할 때 자동으로 계정을 생성할 수 있습니다.
아이디와 비밀번호를 사용한 credentials 방법을 이용한다면, authorize()
메서드 내부에서 백엔드 API /auth/signup
를 호출해서 계정을 생성합니다.
// src/app/api/signup/route.ts
import { NextResponse } from ‘next/server’;
export async function POST(req: Request) {
const { email, password } = await req.json();
// 중간 인증과정 생략
return NextResponse.json({ success: true, msg: ‘’ });
}
클라이언트에서 signIn()
메서드를 호출하면, NextAuth는 내부적으로 /api/auth/signin
API 에 로그인요청을 보내서 처리합니다.
// 회원가입 로직
import { signIn } from ‘next-auth/react’;
async function handleLogin(){
// 기본적으로 redirect 값은 true로 리디렉션된다.
const result = await signIn('cre’dentials, { email: ‘’, password: ‘’, redirect: false });
if(result?.error) console.log(‘로그인 실패: ‘, result.error);
else console.log(‘로그인성공’);
}
signOut()
메서드를 호출하면 NextAuth 내부적으로 /api/auth/signout
API에 로그아웃 요청을 보내서 세션을 삭제하고 쿠키를 만료시킵니다.
// 로그아웃 로직
import { signOut } from ‘next-auth/react’;
<button onClick={()=>signOut()}>로그아웃</button>
useSession()
NextAuth에서 제공하는 리액트 훅으로, 클라이언트 측 컴포넌트에서 현재 사용자의 세션 데이터를 가져오고 상태를 실시간으로 반영할 수 있어서 인증 상태가 변경되면 자동으로 업데이트됩니다.
NextAuth 가 자동으로 내부적으로 /api/auth/session
엔드포인트를 호출해서 세션을 가져옵니다.
import { useSession } from ‘next-auth/react’;
export default function Client(){
const { data: session, status } = useSession();
if(status === ‘loading’) return <p>로딩중…</p>;
if(session) return <p>안녕하세요 {session.user.name}님</p>
return <p>로그인을 해주세요.</p>;
}
클라이언트 요청이 들어오기 전에 서버에서 미리 세션 정보를 가져와서 페이지를 렌더링할 수 있습니다.
클라이언트가 아니라 서버에서 실행되기 때문에 보안적으로 안전합니다.
Next.js 12 이하의 Page Router 방식에서는, 각 페이지 컴포넌트의 getServerSideProps
함수 내부에서 getServerSession
메서드를 사용하여 세션 데이터를 가져옵니다.
// src/pages/protected.tsx
import { getServerSession } from ‘next-auth’;
import { authOptions } from ‘@/api/auth/[…nextauth]’
export async function getServerSideProps(context) {
const session = await getServerSession(context.req, context.res, authOptions);
if(!session) return { redirect: { destination: ‘/’, permanent: false, } };
return { props: { session }, };
}
export default function ProtectedPage({ session }){
return <div>안녕하세요 {session.user.name}</div>
}
Next.js 13부터는 App Router와 서버 컴포넌트 개념이 도입되었습니다.
이로 인해, 각 컴포넌트에서 직접 getServerSideProps
함수를 사용하는 대신, 별도의 auth.ts
파일을 생성해서 세션 관리를 중앙에서 처리할 수 있습니다.
// src/auth.ts
import { getServerSession } from ‘next-auth’;
import { authOptions } from ‘@/app/api/auth/[…nextauth]/route.ts
export async function auth(){
return await getServerSession(authOptions);
}
src/app/protected/page.tsx
import { auth } from ‘@/auth’;
import { redirect } from ‘next/navigation’;
export default async function ProtectedPage(){
const session = await auth();
if(!session) redirect(‘/‘);
return <div>안녕하세요 {session.user.name}</div>;
}
이러한 구조를 통해, 각 서버 컴포넌트에서 getServerSession
함수를 직접 호출하지 않고, auth.ts
파일의 auth
함수를 사용해서 세션을 확인하고 관리할 수 있습니다.
💡getServerSideProps
getServerSideProps
는 Next.js의 Page Router에서 서버 사이드 렌더링을 구현하기 위한 함수입니다.
getServerSideProps
함수는 클라이언트의 페이지 요청마다 서버에서 실행되어 API 엔드포인트에서 데이터를 가져오고, 그 데이터를 페이지 컴포넌트에 props로 전달합니다.
페이지 컴포넌트는getServerSideProps
함수에서 전달받은 props를 사용하여 렌더링해서 HTML을 생성합니다. (= 서버에서 HTML을 렌더링합니다.)
페이지 컴포넌트에서 생성된 HTML은 클라이언트에 전달되어 즉시 화면에 렌더링됩니다.