[TECH-QA] NextAuth.js

๊ธฐ๋ณธ ์„ค์ •

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •

AUTH_SECRET

ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— .env ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ํ•„์š”ํ•œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
AUTH_SECRET=your-secret-key
AUTH_GITHUB_ID=your-github-client-id
AUTH_GITHUB_SECRET=your-github-client-secret
AUTH_SECRET์€ JWT ์•”ํ˜ธํ™”๋ฅผ ์œ„ํ•ด ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค. ext-auth์—์„œ AUTH_SECRET์€ ์„ธ์…˜ ๊ด€๋ฆฌ์™€ JWT(JSON Web Token) ์„œ๋ช…์„ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ๋น„๋ฐ€ ํ‚ค(secret key)์ž…๋‹ˆ๋‹ค. ์ด ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋Š” NextAuth.js๊ฐ€ ์‚ฌ์šฉ์ž ์„ธ์…˜์„ ์•ˆ์ „ํ•˜๊ฒŒ ์•”ํ˜ธํ™”ํ•˜๊ณ  ์ธ์ฆ ํ† ํฐ์„ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๊ฒ€์ฆํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. openssl rand -base64 32 ๋ช…๋ น์–ด๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • AUTH_SECRET์€ ์œ ์ €๋ณ„๋กœ ์ƒ์„ฑ๋˜๊ฑฐ๋‚˜ ์ „๋‹ฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • AUTH_SECRET์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ˆ˜์ค€์—์„œ ์„ค์ •๋˜๋Š” ๋‹จ์ผ ๋น„๋ฐ€ ํ‚ค๋กœ, ๋ชจ๋“  ์‚ฌ์šฉ์ž์— ๋Œ€ํ•ด ๋™์ผํ•˜๊ฒŒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ฆ‰, ๊ฐœ๋ณ„ ์œ ์ €๋งˆ๋‹ค ๋‹ค๋ฅธ AUTH_SECRET์„ ์‚ฌ์šฉํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์ด ํ‚ค๋Š” NextAuth.js๊ฐ€ ์„ธ์…˜ ๊ด€๋ฆฌ์™€ JWT ์„œ๋ช…์„ ์œ„ํ•ด ์„œ๋ฒ„ ์ธก์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ณ ์ •๋œ ๊ฐ’์ž…๋‹ˆ๋‹ค. ์ด ๋ชจ๋“  ์ž‘์—…์€ ์„œ๋ฒ„์—์„œ ์ผ๊ด€๋˜๊ฒŒ ์ด๋ฃจ์–ด์ง€๋ฉฐ, ์œ ์ €๋ณ„๋กœ ๋‹ค๋ฅธ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  • JWT ์„œ๋ช…: next-auth๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ JWT๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์„ธ์…˜์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. AUTH_SECRET์€ JWT๋ฅผ ์„œ๋ช…ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๋น„๋ฐ€ ํ‚ค๋กœ, ์ด ํ‚ค๊ฐ€ ์—†๊ฑฐ๋‚˜ ์ž˜๋ชป๋˜๋ฉด ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  • ์„ธ์…˜ ์•”ํ˜ธํ™”: ์„ธ์…˜ ๋ฐ์ดํ„ฐ(์˜ˆ: ์‚ฌ์šฉ์ž ์ •๋ณด)๋ฅผ ์•”ํ˜ธํ™”ํ•˜์—ฌ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•ฉ๋‹ˆ๋‹ค.
  • CSRF ํ† ํฐ ๋ณดํ˜ธ: next-auth๊ฐ€ CSRF(Cross-Site Request Forgery) ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ƒ์„ฑํ•˜๋Š” ํ† ํฐ์—๋„ ์ด ํ‚ค๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ์œ ์ €๋ณ„ ํ† ํฐ๊ณผ ํ˜ผ๋™ ๊ฐ€๋Šฅ์„ฑ: ๋กœ๊ทธ์ธ ์‹œ ์„œ๋ฒ„๋Š” ์œ ์ €๋ณ„๋กœ ๊ณ ์œ ํ•œ JWT๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ํ† ํฐ์—๋Š” ์œ ์ €์˜ ์ •๋ณด(์˜ˆ: userId, email, role)๊ฐ€ ํฌํ•จ๋˜๋ฉฐ, AUTH_SECRET์„ ์‚ฌ์šฉํ•ด ์„œ๋ช…๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ AUTH_SECRET ์ž์ฒด๋Š” ํ† ํฐ์„ ์ƒ์„ฑ/๊ฒ€์ฆํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๊ณ ์ •๋œ ํ‚ค์ผ ๋ฟ์ž…๋‹ˆ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ๋กœ ์ „๋‹ฌ๋˜์ง€ ์•Š์Œ: AUTH_SECRET์€ ์ ˆ๋Œ€ ํด๋ผ์ด์–ธํŠธ(๋ธŒ๋ผ์šฐ์ €)๋กœ ์ „๋‹ฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์„œ๋ฒ„ ๋‚ด๋ถ€์—์„œ๋งŒ ์‚ฌ์šฉ๋˜๋Š” ๋น„๋ฐ€ ๊ฐ’์œผ๋กœ, ์œ ์ €์™€ ์ง์ ‘์ ์ธ ์ƒํ˜ธ์ž‘์šฉ์ด ์—†์Šต๋‹ˆ๋‹ค.
  • ์„ธ์…˜ ๋ฐ์ดํ„ฐ: NextAuth.js๋Š” ์„ธ์…˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(์˜ต์…˜) ๋˜๋Š” JWT ์ž์ฒด์— ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ ์ €๋ณ„ ์ •๋ณด๋Š” ์ด ์„ธ์…˜ ๋ฐ์ดํ„ฐ์— ์ €์žฅ๋˜๋ฉฐ, AUTH_SECRET์€ ์ด ๋ฐ์ดํ„ฐ๋ฅผ ์•”ํ˜ธํ™”ํ•˜๊ฑฐ๋‚˜ ๋ณดํ˜ธํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ์„œ๋ฒ„๋Š” ์š”์ฒญ๋งˆ๋‹ค AUTH_SECRET์œผ๋กœ JWT๋ฅผ ๊ฒ€์ฆํ•ด ์œ ์ €๋ฅผ ์ธ์ฆ.
๊ฐ’์€ ์•ˆ์ „ํ•˜๊ณ  ์˜ˆ์ธกํ•˜๊ธฐ ์–ด๋ ค์šด ๋ฌธ์ž์—ด์ด์–ด์•ผ ํ•˜๋ฉฐ, ์ตœ์†Œ 32์ž ์ด์ƒ์˜ ๋ฌด์ž‘์œ„ ๋ฌธ์ž์—ด์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ ๋ช…๋ น์–ด๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
openssl rand -base64 32
App Router๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, /app/api/auth/[...nextauth]/route.ts ํŒŒ์ผ์—์„œ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์€ NextAuth๊ฐ€ ์ธ์ฆ ๊ด€๋ จ ์š”์ฒญ(๋กœ๊ทธ์ธ, ๋กœ๊ทธ์•„์›ƒ, ์„ธ์…˜ ๊ด€๋ฆฌ ๋“ฑ)์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์—”๋“œํฌ์ธํŠธ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

๋งŒ์•ฝ Pages Router(/pages)๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, /pages/api/auth/[...nextauth].ts ํŒŒ์ผ์—์„œ ๋น„์Šทํ•œ ๋ฐฉ์‹์œผ๋กœ ์„ค์ •ํ•˜์ง€๋งŒ, App Router์—์„œ๋Š” route.ts ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, App Router์—์„œ๋Š” GET, POST ๋“ฑ์˜ HTTP ๋ฉ”์„œ๋“œ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๋‚ด๋ณด๋‚ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • App Router: App Router์—์„œ๋Š” next-auth์˜ ์ตœ์‹  ๋ฒ„์ „(4.x)์„ ์‚ฌ์šฉํ•˜๊ณ , next-auth/middleware๋ฅผ ํ™œ์šฉํ•ด ๋ณดํ˜ธ๋œ ๋ผ์šฐํŠธ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฏธ๋“ค์›จ์–ด: ์ธ์ฆ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€๋ฅผ ๋ณดํ˜ธํ•˜๋ ค๋ฉด /app/middleware.ts์—์„œ NextAuth ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ํ™˜๊ฒฝ ๋ณ€์ˆ˜: NEXTAUTH_SECRET๊ณผ ์ œ๊ณต์ž(์˜ˆ: Google, GitHub)์˜ ํด๋ผ์ด์–ธํŠธ ID/์‹œํฌ๋ฆฟ์€ .env ํŒŒ์ผ์— ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ์˜ˆ์‹œ

my-nextjs-app/
โ”œโ”€โ”€ app/
โ”‚   โ”œโ”€โ”€ api/
โ”‚   โ”‚   โ”œโ”€โ”€ auth/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ [...nextauth]/
โ”‚   โ”‚   โ”‚       โ””โ”€โ”€ route.ts         # NextAuth ์„ค์ • (์ธ์ฆ ์—”๋“œํฌ์ธํŠธ)
โ”‚   โ”œโ”€โ”€ (auth)/
โ”‚   โ”‚   โ”œโ”€โ”€ login/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ page.tsx            # ์ปค์Šคํ…€ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€
โ”‚   โ”‚   โ”œโ”€โ”€ signup/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ page.tsx            # ์ปค์Šคํ…€ ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€ (์„ ํƒ)
โ”‚   โ”œโ”€โ”€ dashboard/
โ”‚   โ”‚   โ””โ”€โ”€ page.tsx                # ๋ณดํ˜ธ๋œ ํŽ˜์ด์ง€ (์ธ์ฆ ํ•„์š”)
โ”‚   โ”œโ”€โ”€ layout.tsx                  # ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ
โ”‚   โ”œโ”€โ”€ page.tsx                    # ํ™ˆ ํŽ˜์ด์ง€
โ”‚   โ””โ”€โ”€ globals.css                 # ์ „์—ญ ์Šคํƒ€์ผ
โ”œโ”€โ”€ lib/
โ”‚   โ”œโ”€โ”€ auth.ts                     # NextAuth ์„ค์ • (authOptions)
โ”‚   โ””โ”€โ”€ db.ts                       # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ (์˜ˆ: Prisma)
โ”œโ”€โ”€ components/
โ”‚   โ”œโ”€โ”€ Header.tsx                  # ํ—ค๋” ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”œโ”€โ”€ Footer.tsx                  # ํ‘ธํ„ฐ ์ปดํฌ๋„ŒํŠธ
โ”‚   โ””โ”€โ”€ AuthButton.tsx              # ๋กœ๊ทธ์ธ/๋กœ๊ทธ์•„์›ƒ ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ
โ”œโ”€โ”€ public/
โ”‚   โ”œโ”€โ”€ images/                     # ์ •์  ์ด๋ฏธ์ง€ ํŒŒ์ผ
โ”‚   โ””โ”€โ”€ favicon.ico                 # ํŒŒ๋น„์ฝ˜
โ”œโ”€โ”€ middleware.ts                   # NextAuth ๋ฏธ๋“ค์›จ์–ด (๋ณดํ˜ธ๋œ ๋ผ์šฐํŠธ ์„ค์ •)
โ”œโ”€โ”€ .env                            # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ (NEXTAUTH_SECRET, Google ID ๋“ฑ)
โ”œโ”€โ”€ next.config.mjs                 # Next.js ์„ค์ •
โ”œโ”€โ”€ tsconfig.json                   # TypeScript ์„ค์ • (TypeScript ์‚ฌ์šฉ ์‹œ)
โ”œโ”€โ”€ package.json                    # ํ”„๋กœ์ ํŠธ ์˜์กด์„ฑ ๋ฐ ์Šคํฌ๋ฆฝํŠธ
โ””โ”€โ”€ README.md                       # ํ”„๋กœ์ ํŠธ ์„ค๋ช…
  • /app/(auth)/login/page.tsx : ์ปค์Šคํ…€ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ, signIn ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ UI๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”์— ๋”ฐ๋ผ ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€(/signup) ๋“ฑ์„ ์ถ”๊ฐ€.
  • /app/dashboard/page.tsx : ์ธ์ฆ์ด ํ•„์š”ํ•œ ๋ณดํ˜ธ๋œ ํŽ˜์ด์ง€. NextAuth ๋ฏธ๋“ค์›จ์–ด๋กœ ์ ‘๊ทผ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Œ.

๐Ÿ“ app/api/auth/[...nextauth]/route.ts

NextAuth์˜ ์ธ์ฆ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํŒŒ์ผ์ž…๋‹ˆ๋‹ค. GET, POST ๋ฉ”์„œ๋“œ๋กœ NextAuth๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
import NextAuth from "next-auth";
import GitHubProvider from "next-auth/providers/github";

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    GitHubProvider({
      clientId: process.env.AUTH_GITHUB_ID,
      clientSecret: process.env.AUTH_GITHUB_SECRET,
    }),
  ],
});

export { handlers as GET, handlers as POST };
NextAuth(authOptions)๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ Google, GitHub ๋“ฑ์˜ ์ธ์ฆ ์ œ๊ณต์ž๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ฉฐ handlers๋Š” ์ธ์ฆ ๊ด€๋ จ API ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ /lib/auth.js

NextAuth ์„ค์ •(authOptions)์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. GoogleProvider, ์„ธ์…˜ ์ฝœ๋ฐฑ, JWT ์„ค์ • ๋“ฑ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";

export const {
  handlers: { GET, POST },
  auth,
  signIn,
  signOut,
} = NextAuth({
  providers: [
    CredentialsProvider({
      name: "Credentials",
      credentials: {
        username: { label: "Username", type: "text", placeholder: "jsmith" },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials) {
        const authResponse = await fetch("/your/endpoint", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(credentials),
        });

        if (!authResponse.ok) {
          return null;
        }
        const user = await authResponse.json();
        return user;
      },
    }),
  ],
});

ํด๋ผ์ด์–ธํŠธ ์‚ฌ์šฉ๋ฒ•

SessionProvider ์‚ฌ์šฉ

ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” next-auth/react์˜ ํ›…(useSession, signIn, signOut ๋“ฑ)์„ ์‚ฌ์šฉํ•ด ์ธ์ฆ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ฑฐ๋‚˜ ๋กœ๊ทธ์ธ/๋กœ๊ทธ์•„์›ƒ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ app/layout.tsx

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

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>
        <SessionProvider>{children}</SessionProvider>
      </body>
    </html>
  );
}
App Router์—์„œ๋Š” SessionProvider๊ฐ€ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์ด๋ฏ€๋กœ, ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ์— ์ง์ ‘ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ณ„๋„์˜ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ pages/_app.tsx


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

export default function App({ Component, pageProps: { session, ...pageProps } }) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />
    </SessionProvider>
  );
}

ํด๋ผ์ด์–ธํŠธ ์‚ฌ์šฉ๋ฒ•

useSession ์‚ฌ์šฉ

ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” next-auth/react์˜ ํ›…(useSession, signIn, signOut ๋“ฑ)์„ ์‚ฌ์šฉํ•ด ์ธ์ฆ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ฑฐ๋‚˜ ๋กœ๊ทธ์ธ/๋กœ๊ทธ์•„์›ƒ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ useSession ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ธ์…˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
// app/client/page.tsx

"use client";

import { useSession, signIn, signOut } from "next-auth/react";

export default function ClientPage() {
  const { data: session, status } = useSession();

  if (status === "loading") return <p>๋กœ๋”ฉ ์ค‘...</p>;
  if (status === "authenticated") {
    return (
      <>
        <p>{session.user?.name}๋‹˜์œผ๋กœ ๋กœ๊ทธ์ธ๋จ</p>
        <button onClick={() => signOut()}>๋กœ๊ทธ์•„์›ƒ</button>
      </>
    );
  }

  return (
    <>
      <p>๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์Œ</p>
      <button onClick={() => signIn("github")}>GitHub๋กœ ๋กœ๊ทธ์ธ</button>
    </>
  );
}
  • useSession์€ data (์„ธ์…˜ ๊ฐ์ฒด)์™€ status (loading, authenticated, unauthenticated)๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • signIn๊ณผ signOut์€ ๊ฐ๊ฐ ๋กœ๊ทธ์ธ๊ณผ ๋กœ๊ทธ์•„์›ƒ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์„ธ์…˜ ์—…๋ฐ์ดํŠธ

useSession์˜ update ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์„ธ์…˜์„ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
// app/client/page.tsx
"use client";

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

export default function UpdateSessionPage() {
  const { data: session, update } = useSession();

  const handleUpdate = async () => {
    await update({ name: "์ƒˆ๋กœ์šด ์ด๋ฆ„" }); // ์„ธ์…˜ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ
  };

  return (
    <>
      <p>ํ˜„์žฌ ์‚ฌ์šฉ์ž: {session?.user?.name}</p>
      <button onClick={handleUpdate}>์ด๋ฆ„ ์—…๋ฐ์ดํŠธ</button>
    </>
  );
}
update ๋ฉ”์„œ๋“œ๋Š” ์„œ๋ฒ„์˜ jwt ์ฝœ๋ฐฑ์„ ํŠธ๋ฆฌ๊ฑฐํ•˜์—ฌ ์„ธ์…˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.

<์„œ๋ฒ„ ์ธก ์‚ฌ์šฉ๋ฒ•> App Router์—์„œ ์„œ๋ฒ„ ์„ธ์…˜ ํ™•์ธ

๊ด€๋ จ ํ‚ค์›Œ๋“œ

auth ํ•จ์ˆ˜

/app/api/auth/[...nextauth]/route.ts์—์„œ ๋‚ด๋ณด๋‚ธ auth ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ app/server/page.tsx

import { auth , GET, POST } from @/app/auth";

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

  if (!session) {
    return <p>๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.</p>;
  }

  return <p>์„œ๋ฒ„์—์„œ ํ™•์ธ๋œ ์‚ฌ์šฉ์ž: {session.user?.name}</p>;
}
auth ํ•จ์ˆ˜๋Š” ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ, ๋ฏธ๋“ค์›จ์–ด, API ๋ผ์šฐํŠธ ๋“ฑ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ†ตํ•ฉ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.

<์„œ๋ฒ„ ์ธก ์‚ฌ์šฉ๋ฒ•> Pages Router์—์„œ ์„œ๋ฒ„ ์„ธ์…˜ ํ™•์ธ

Pages Router์—์„œ๋Š” getServerSession์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ pages/server.js

import { getServerSession } from "next-auth/next";
import { authOptions } from "./api/auth/[...nextauth]";

export default function ServerPage({ session }) {
  if (!session) {
    return <p>๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.</p>;
  }

  return <p>์„œ๋ฒ„์—์„œ ํ™•์ธ๋œ ์‚ฌ์šฉ์ž: {session.user?.email}</p>;
}

export async function getServerSideProps(context) {
  const session = await getServerSession(context.req, context.res, authOptions);
  return {
    props: {
      session,
    },
  };
}
getServerSession์€ ์„ธ์…˜ ํ™•์ธ ์†๋„๊ฐ€ ๋น ๋ฅด๋ฉฐ, ์ถ”๊ฐ€ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์ค„์ž…๋‹ˆ๋‹ค.

๋ฏธ๋“ค์›จ์–ด๋กœ ํŽ˜์ด์ง€ ๋ณดํ˜ธ

๐Ÿ“ /middleware.ts

NextAuth ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์„ค์ •ํ•˜์—ฌ ํŠน์ • ๊ฒฝ๋กœ(์˜ˆ: /dashboard)์— ์ธ์ฆ์ด ํ•„์š”ํ•˜๋„๋ก ์ œํ•œ. ์˜ˆ: export { default } from 'next-auth/middleware';
import { withAuth } from "next-auth/middleware";

export default withAuth({
  callbacks: {
    authorized: ({ token }) => !!token, // ํ† ํฐ์ด ์žˆ์œผ๋ฉด ์ธ์ฆ๋œ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ
  },
});

export const config = {
  matcher: ["/dashboard/:path*"], // /dashboard ๊ฒฝ๋กœ์™€ ํ•˜์œ„ ๊ฒฝ๋กœ ๋ณดํ˜ธ
};
/dashboard๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ชจ๋“  ๊ฒฝ๋กœ์— ๋Œ€ํ•ด ์ธ์ฆ์„ ์š”๊ตฌ

์ปค์Šคํ…€ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€

๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•˜๋ ค๋ฉด pages ์˜ต์…˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ app/api/auth/[...nextauth]/route.ts

export const { handlers, auth } = NextAuth({
  providers: [GitHubProvider({ clientId: process.env.AUTH_GITHUB_ID, clientSecret: process.env.AUTH_GITHUB_SECRET })],
  pages: {
    signIn: "/auth/signin",
  },
});

๐Ÿ“ app/auth/signin/page.tsx

import { getProviders, signIn } from "next-auth/react";

export default async function SignIn() {
  const providers = await getProviders();

  return (
    <div>
      <h1>๋กœ๊ทธ์ธ</h1>
      {providers &&
        Object.values(providers).map((provider) => (
          <div key={provider.name}>
            <button onClick={() => signIn(provider.id)}>
              {provider.name}์œผ๋กœ ๋กœ๊ทธ์ธ
            </button>
          </div>
        ))}
    </div>
  );
}