Next.jsNext.js 기초 · 6기초

인증 구현 — NextAuth.js로 소셜 로그인

Next.jsNextAuth인증소셜로그인세션

NextAuth.js 설치

npm install next-auth@beta

기본 설정

// auth.ts (루트에 위치)
import NextAuth from "next-auth";
import Google from "next-auth/providers/google";
import GitHub from "next-auth/providers/github";
import Credentials from "next-auth/providers/credentials";
import { db } from "@/lib/db";

export const { handlers, signIn, signOut, auth } = NextAuth({
    providers: [
        Google({
            clientId: process.env.GOOGLE_CLIENT_ID!,
            clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
        }),
        GitHub({
            clientId: process.env.GITHUB_CLIENT_ID!,
            clientSecret: process.env.GITHUB_CLIENT_SECRET!,
        }),
        Credentials({
            credentials: {
                email: { label: "이메일", type: "email" },
                password: { label: "비밀번호", type: "password" },
            },
            async authorize(credentials) {
                const user = await db.users.findByEmail(credentials.email as string);
                if (!user) return null;

                const valid = await verifyPassword(
                    credentials.password as string,
                    user.hashedPassword
                );
                if (!valid) return null;

                return { id: user.id, name: user.name, email: user.email };
            },
        }),
    ],
    callbacks: {
        async session({ session, token }) {
            if (token.sub) session.user.id = token.sub;
            return session;
        },
    },
});

API Route Handler 연결

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

export const { GET, POST } = handlers;

환경 변수

# .env.local
AUTH_SECRET=your-secret-key  # openssl rand -base64 32

GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...

GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...

로그인/로그아웃 버튼

// components/AuthButtons.tsx
import { signIn, signOut, auth } from "@/auth";

// 서버 컴포넌트에서 세션 확인
export default async function AuthButtons() {
    const session = await auth();

    if (session?.user) {
        return (
            <div>
                <p>안녕하세요, {session.user.name}님!</p>
                <form action={async () => {
                    "use server";
                    await signOut();
                }}>
                    <button type="submit">로그아웃</button>
                </form>
            </div>
        );
    }

    return (
        <div>
            <form action={async () => {
                "use server";
                await signIn("google");
            }}>
                <button>Google로 로그인</button>
            </form>
            <form action={async () => {
                "use server";
                await signIn("github");
            }}>
                <button>GitHub로 로그인</button>
            </form>
        </div>
    );
}

미들웨어로 라우트 보호

// middleware.ts
import { auth } from "@/auth";

export default auth((req) => {
    const isLoggedIn = !!req.auth;
    const pathname = req.nextUrl.pathname;

    if (!isLoggedIn && pathname.startsWith("/dashboard")) {
        return Response.redirect(new URL("/login", req.url));
    }
});

export const config = {
    matcher: ["/dashboard/:path*"],
};

클라이언트에서 세션 사용

"use client";
import { useSession } from "next-auth/react";

function UserMenu() {
    const { data: session, status } = useSession();

    if (status === "loading") return <span>로딩 중...</span>;
    if (!session) return <a href="/api/auth/signin">로그인</a>;

    return (
        <div>
            <img src={session.user.image ?? ""} alt="아바타" />
            <span>{session.user.name}</span>
        </div>
    );
}

정리

flowchart LR
    USER["사용자"]
    GOOGLE["Google OAuth"]
    GITHUB["GitHub OAuth"]
    NEXTAUTH["NextAuth.js"]
    SESSION["세션/JWT"]
    APP["보호된 페이지"]

    USER -->|"로그인 클릭"| NEXTAUTH
    NEXTAUTH --> GOOGLE
    NEXTAUTH --> GITHUB
    GOOGLE & GITHUB -->|"콜백"| NEXTAUTH
    NEXTAUTH --> SESSION
    SESSION -->|"인증됨"| APP
항목설명
handlersAPI Route에 연결
auth()서버 컴포넌트 세션 확인
signIn()로그인
signOut()로그아웃
useSession()클라이언트 세션 훅

다음 편에서는 환경 변수와 배포 — .env 설정과 Vercel 배포 방법을 배웁니다.

궁금한 점이 있으신가요?

협업·의뢰는 아래로, 가벼운 소통은 인스타그램 @bluefox._.hi도 환영이에요.