๐Ÿ”next-auth ์ธ์ฆ(authentication) ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (1)

์›ฐ์น˜์Šคยท2025๋…„ 1์›” 7์ผ

Next.js

๋ชฉ๋ก ๋ณด๊ธฐ
2/3

https://next-auth.js.org/ ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€

๐Ÿ“„ ์ฃผ์š” ํŠน์ง•

1. ๋‹ค์–‘ํ•œ ์ธ์ฆ ์ œ๊ณต์ž ์ง€์›

  • next-auth๋Š” Google, Facebook, GitHub, Twitter ๋“ฑ ๋‹ค์–‘ํ•œ OAuth ์ œ๊ณต์ž์™€์˜ ํ†ตํ•ฉ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฉ”์ผ/๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ด์šฉํ•œ ์ธ์ฆ์ด๋‚˜ Magic Link(์ด๋ฉ”์ผ ๋งํฌ๋ฅผ ํ†ตํ•œ ๋กœ๊ทธ์ธ) ๋ฐฉ์‹๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

2. ์„ธ์…˜ ๊ด€๋ฆฌ

  • next-auth๋Š” ์‚ฌ์šฉ์ž์˜ ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ์„ธ์…˜์œผ๋กœ ๊ด€๋ฆฌํ•˜๋ฉฐ, ์„œ๋ฒ„ ์ธก ๋ฐ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ
    ์„ธ์…˜ ์ •๋ณด๋ฅผ ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • JWT(JSON Web Tokens)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ธ์…˜์„ ๊ด€๋ฆฌํ•˜๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์„ธ์…˜์„ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ๋ณด์•ˆ

  • CSRF(๊ต์ฐจ ์‚ฌ์ดํŠธ ์š”์ฒญ ์œ„์กฐ) ๊ณต๊ฒฉ ๋ฐฉ์ง€ ๋ฐ ๋‹ค์–‘ํ•œ ๋ณด์•ˆ ๊ธฐ๋Šฅ์ด ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.
  • ์•”ํ˜ธํ™”๋œ ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

4. ํ™•์žฅ์„ฑ

  • ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ์šฉ์ดํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ •์˜ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€, ์ธ์ฆ ๋กœ์ง, ์ฝœ๋ฐฑ ๋“ฑ์„ ์†์‰ฝ๊ฒŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์–ด๋Œ‘ํ„ฐ๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ์„œ๋ฒ„๋ฆฌ์Šค ํ™˜๊ฒฝ์— ์ตœ์ ํ™”

  • Next.js์˜ API ๋ผ์šฐํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋ฒ„๋ฆฌ์Šค ํ™˜๊ฒฝ์—์„œ๋„ ํšจ์œจ์ ์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ณ„๋„์˜ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋ฅผ ๊ตฌ์ถ•ํ•  ํ•„์š” ์—†์ด ์ธ์ฆ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“„ ์ฃผ์š” ๊ฐœ๋…

1.Provider

  • OAuth ์ œ๊ณต์ž๋‚˜ ์ด๋ฉ”์ผ ์ธ์ฆ ๊ฐ™์€ ๋ฐฉ๋ฒ•์„ ์„ค์ •ํ•˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.
  • ์˜ˆ: Google, GitHub ๋“ฑ์˜ ์™ธ๋ถ€ ์ธ์ฆ ์ œ๊ณต์ž.

2. Session

  • ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ •๋ณด์ž…๋‹ˆ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๋ชจ๋‘์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

3. Callbacks

  • ์ธ์ฆ ํ๋ฆ„ ์ค‘ ํŠน์ • ์ด๋ฒคํŠธ์— ๋Œ€ํ•œ ํ›„์† ์ฒ˜๋ฆฌ๋ฅผ ์ •์˜ํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค
  • ์˜ˆ: ๋กœ๊ทธ์ธ ํ›„ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๊ฑฐ๋‚˜, JWT์— ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จ์‹œํ‚ค๋Š” ๋“ฑ์˜ ์ž‘์—…

4. Database Adapters

  • ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ๋ฐ ์„ธ์…˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ์–ด๋Œ‘ํ„ฐ์ž…๋‹ˆ๋‹ค
  • MongoDB, PostgreSQL, MySQL ๋“ฑ ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค
// pages/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import GithubProvider from "next-auth/providers/github";
import GoogleProvider from "next-auth/providers/google";

export default NextAuth({
  providers: [
    Providers.GitHub({
      clientId: process.env.GITHUB_CLIENT_ID,
      clientSecret: process.env.GITHUB_CLIENT_SECRET,
    }),
    // ๋‹ค๋ฅธ ์ œ๊ณต์ž๋„ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
  ],
  callbacks: {
    async session(session, token) {
      session.user.id = token.id;
      return session;
    },
    async jwt(token, user) {
      if (user) {
        token.id = user.id;
      }
      return token;
    },
  },
});

next-auth๋Š” Next.jsd์—์„œ ์ธ์ฆ ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ์ถ”๊ฐ€ํ•˜๊ณ  ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. OAuth ์ œ๊ณต์ž์™€ ์ด๋ฉ”์ผ ์ธ์ฆ์„ ํฌํ•จํ•œ ๋‹ค์–‘ํ•œ ์ธ์ฆ ๋ฐฉ์‹์„ ์ง€์›ํ•˜๋ฉฐ, ์‚ฌ์šฉ์ž ์ •์˜๊ฐ€ ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค. โฉ ์‚ฌ์šฉ์ž ์ธ์ฆ๊ณผ ๊ด€๋ จ๋œ ๋ณต์žกํ•œ ์ž‘์—…์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ” Credentials ์ธ์ฆ

  • ์‚ฌ์ „์  ์˜๋ฏธ : ์ž๊ฒฉ ์ฆ๋ช…
  • next-auth์—์„œ ์ œ๊ณตํ•˜๋Š” Credentials ์ธ์ฆ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์•„์ด๋””/๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ •์˜ ๋กœ๊ทธ์ธ ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„
  • ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ ์™ธ๋ถ€ API๋ฅผ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, OAuth์™€ ๊ฐ™์€ ์™ธ๋ถ€ ํ”„๋กœ๋ฐ”์ด๋” ์—†์ด ์‚ฌ์šฉ์ž ์ž๊ฒฉ ์ฆ๋ช…์„ ์ฒ˜๋ฆฌ

์ฃผ์š” ํŠน์ง•

  1. ์œ ์—ฐํ•œ ์‚ฌ์šฉ์ž ์ธ์ฆ
    • CredentialsProvider ๋Š” ์ด๋ฉ”์ผ/๋น„๋ฐ€๋ฒˆํ˜ธ, ์‚ฌ์šฉ์ž ์ด๋ฆ„/๋น„๋ฐ€๋ฒˆํ˜ธ, ๋˜๋Š” ๊ทธ ์™ธ ํ•„์š”ํ•œ ์‚ฌ์šฉ์ž ์ •์˜ ๋ฐ์ดํ„ฐ๋กœ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ์Œ
    • ์™ธ๋ถ€ API๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ํ†ตํ•ฉํ•˜์—ฌ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ
  2. ์‚ฌ์šฉ์ž ์ •์˜ ์ž…๋ ฅ
    • ๋กœ๊ทธ์ธ ํผ์—์„œ ์‚ฌ์šฉ์ž ์ •์˜ ํ•„๋“œ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, credentials ์„ค์ •์—์„œ ์ด๋Ÿฌํ•œ ํ•„๋“œ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ๋ณด์•ˆ
    • ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด HTTPS๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ•ด์‹ฑ ๋ฐ ์ €์žฅ ์ „ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

์„ค์ • ๋ฐฉ๋ฒ•

  1. CredentialsProvider ์„ค์ •
    next-auth์˜ NextAuth ์„ค์ •์—์„œ CredentialsProvider ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ •์˜ ์ธ์ฆ ์„ค์ •
app/api/auth/[...nextauth]/route.ts

import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';

const handler = NextAuth({
    providers: [
        CredentialsProvider({
            name: "Credentials",
            credentials: {
                email: { label: "Email", type: "text" },
                password: { label: "Password", type: "password" },
            },
            async authorize(credentials, req) {
                // ์‚ฌ์šฉ์ž ์ธ์ฆ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ถ€๋ถ„ (DB์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฒ€์ฆ)
                const params = {
                    email: credentials?.email,
                    password: credentials?.password
                };
                const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/login/`, {
                   method: 'POST',
                   headers: {
                    "Content-Type": "application/json"
                   },
                   body: JSON.stringify(params)
                });

                const res = await response.json();

                if (res.message === "OK") {
                    return res;
                } else {
                    throw new Error(res.message);
                }
            },
        }),
    ],
    // ์ถ”๊ฐ€ ์˜ต์…˜ ์„ค์ • (์˜ˆ: ์„ธ์…˜ ๊ด€๋ฆฌ)
});

export { handler as GET, handler as POST }

์ค‘์š”ํ•œ ์ :

- `credentials` ๊ฐ์ฒด์˜ ํ‚ค๋Š” ๋กœ๊ทธ์ธ ์ฐฝ์—์„œ ๋ณด๋‚ธ `input` ํ•„๋“œ์˜ `name` ์†์„ฑ๊ณผ ์ผ์น˜ํ•ด์•ผ ํ•จ
- `email`๊ณผ `password`๋ฅผ ๋ณด๋‚ธ๋‹ค๋ฉด, `CredentialsProvider`์—์„œ๋„ `email`๊ณผ `password`๋กœ ๋ฐ›์•„์•ผ ํ•จ
  1. ์‚ฌ์šฉ์ž ์ธ์ฆ ๋กœ์ง
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ API ํ˜ธ์ถœ์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ credentials์ด ์œ ํšจํ•œ์ง€ ๊ฒ€์ฆ
app/api/login/route.ts

import { connectToDatabase } from '@/app/lib/mongodb';
import { NextRequest, NextResponse } from 'next/server';
import { Users, UsersType } from '../models/user';

// 1. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์•„์ด๋”” -> DB ์— ๊ณ„์ • ์กฐํšŒ
// 2. ๊ณ„์ •์ด ์กด์žฌํ•˜๋Š”์ง€ ์ฒดํฌ
// 2-1. ๊ณ„์ • ์กด์žฌํ•˜์ง€ ์•Š๋‹ค๋ฉด ๊ฒฐ๊ณผ ๋ฆฌํ„ด
// 2-2. ๊ณ„์ •์ด ์กด์žฌํ•œ๋‹ค๋ฉด ์ž…๋ ฅ๋œ ํŒจ์Šค์›Œ๋“œ๋ฅผ bcrypt.compare ํ•จ์ˆ˜๋กœ ํŒจ์Šค์›Œ๋“œ๋ฅผ ๋น„๊ตํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ด

const bcrypt = require("bcrypt");

export async function POST(request: NextRequest) {
  try {
    // ์š”์ฒญ ๋ณธ๋ฌธ(body) ๊ฐ€์ ธ์˜ค๊ธฐ
    const body = await request.json();

    // ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณด๋‚ธ ๋ฐ์ดํ„ฐ ์ถœ๋ ฅ
    console.log('Login Server Received data:', body);

    const dbName = process.env.DB_NAME_CHICKEN;
    const collectionName = process.env.COLLECTION_USERS;

    console.log('@@@ Before connecting to database');

    const db = await connectToDatabase(dbName as string);
    const collection = db.collection(collectionName as string);

    console.log(`-- Database connected`);
    console.log(`-- Connected to databse: ${db.databaseName}`);

    const inputPassword: String = body.password;
    const userInfo: UsersType = await Users.findOne({ email: body.email}).exec();

    if (userInfo === null) {
      return Response.json({
        message: "๊ณ„์ •์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.",
        result: ""
      })
    }

    const isMatched: boolean = await bcrypt.compare(inputPassword, userInfo.password);
    
    return NextResponse.json({
      message: isMatched ? 'OK !' : '์•„์ด๋”” ํ˜น์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.'
    });
  } catch (error) {
    console.error('Error handling request:', error);
    return NextResponse.json({ message: 'Error processing request' }, { status: 500 });
  }
}
  1. ๋กœ๊ทธ์ธ ํผ
  • signIn ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.
    NextAuth.js์—์„œ ์ œ๊ณตํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก ํ•จ์ˆ˜๋กœ, ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆ์„ ์‹œ๋„ํ•  ๋•Œ ํ˜ธ์ถœํ•œ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์„ฑ๊ณต ์—ฌ๋ถ€ ๋ฐ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ํฌํ•จํ•œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    โฉcredentials ์ธ์ฆ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„๋กœ ์ „์†ก

๐Ÿ”signIn ํ•จ์ˆ˜์˜ ์‘๋‹ต ๊ฐ’

const result = await signIn(provider, options);

์ฃผ์š” ๋ฐ˜ํ™˜ ๊ฐ’ ์†์„ฑ

  • result.ok: (boolean) ๋กœ๊ทธ์ธ ์‹œ๋„๊ฐ€ ์„ฑ๊ณตํ–ˆ๋Š”์ง€ ์—ฌ๋ถ€. true์ผ ๊ฒฝ์šฐ ์„ฑ๊ณต, false์ผ ๊ฒฝ์šฐ ์‹คํŒจ.
  • result.error: (string | undefined) ๋กœ๊ทธ์ธ ์‹คํŒจ ์‹œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํฌํ•จ. ์„ฑ๊ณต ์‹œ์—๋Š” undefined.
  • result.status: (number | undefined) HTTP ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ. ์„ฑ๊ณต ์‹œ์—๋Š” undefined์ผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์‹คํŒจ ์‹œ HTTP ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ์Œ.
  • result.url: (string | null | undefined) ๋ฆฌ๋””๋ ‰์…˜๋  URL. redirect: false๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์ด ์†์„ฑ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
์‹ค์ œ result json ๊ฐ’
๋กœ๊ทธ์ธ ์„ฑ๊ณตํ•œ ๊ฒฝ์šฐ -> {"error":null,"status":200,"ok":true,"url":"http://localhost:3000"}
์‹คํŒจํ•œ ๊ฒฝ์šฐ -> {"error":"CredentialsSignin","status":401,"ok":false,"url":null}
function Login() {
    // ๋กœ๊ทธ์ธ ์ •๋ณด
    const [formData, setFormData] = useState({
        email: '',
        password: ''
    });

    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        if (!e.target) return;
        const { name, value } = e.target;
        setFormData({
            ...formData,
            [name]: value
        });
    };

    const router = useRouter();

    const handleSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        
        // ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
        console.log(`๋กœ๊ทธ์ธ => email: ${formData.email}, password: ${formData.password}`);

        try {
            const res = await signIn("credentials", {
                redirect: false,
                email: formData.email,
                password: formData.password,
                callbackUrl: "/",
            }).then((result) => {
                console.log(result!.error);

                if (result?.ok) {
                    console.log('์ธ์ฆ์— ์„ฑ๊ณตํ•˜์˜€์Šต๋‹ˆ๋‹ค.');
                    router.push("/");   // ์ธ์ฆ ์„ฑ๊ณต ํ›„ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ. ํ™ˆ์œผ๋กœ ์„ค์ •
                }
                else {
                    showToast.error(result?.error ?? '๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
                 //   return;
                }
            });
        } catch (error) {
            console.error(`Network error: ${error}`);
        }
      };
  
  return (
    <form onSubmit={handleSubmit}>
    	<input type="text" name="email" className="grow" placeholder="์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”." 
		onChange={handleChange} />
        <input
            type={showPassword ? 'text' : 'password'}
            name="password"
            className="grow"
            placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
            onChange={handleChange}
        />
              
        <button type="submit" className={'btn my-6 ylw w-full'}>๋กœ๊ทธ์ธ</button>
    </form>
   );
}

export default Login;

์ถ”๊ฐ€ ๊ณ ๋ ค ์‚ฌํ•ญ

  1. ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณด์•ˆ:

    • ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์•ˆ์ „ํ•œ ํ•ด์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜(Bcrypt, Argon2 ๋“ฑ)์œผ๋กœ ํ•ด์‹ฑํ•œ ํ›„ ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ๋กœ๊ทธ์ธ ์‹œ ์ž…๋ ฅ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ•ด์‹œํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ•ด์‹œ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
  2. ์„ธ์…˜ ๊ด€๋ฆฌ:

    • next-auth๋Š” ์„ธ์…˜ ๊ด€๋ฆฌ๋ฅผ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉฐ, ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธ ํ›„ ์ธ์ฆ๋œ ์„ธ์…˜์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ์—๋Ÿฌ ์ฒ˜๋ฆฌ:

    • signIn ํ•จ์ˆ˜์˜ ๊ฒฐ๊ณผ์—์„œ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ™•์ธํ•˜๊ณ , ์‚ฌ์šฉ์ž์—๊ฒŒ ์ ์ ˆํ•œ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

next-auth์˜ Credentials ์ธ์ฆ์€ ์‚ฌ์šฉ์ž ์ •์˜ ์ธ์ฆ ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•˜๊ณ  ์œ ์—ฐํ•œ ๋„๊ตฌ. ์‚ฌ์šฉ์ž ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ์„ค์ •ํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋˜๋Š” ์™ธ๋ถ€ API์™€ ์—ฐ๋™ํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋ฉฐ, authorize ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์„ธ๋ถ€์ ์ธ ์ธ์ฆ ๋กœ์ง์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Œ

์„ธ์…˜ ๊ด€๋ฆฌ

  1. ์‚ฌ์šฉ์ž ์ธ์ฆ:
  • authorize ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ž๊ฒฉ ์ฆ๋ช…(์˜ˆ: ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ)์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
    ๊ฒ€์ฆ์— ์„ฑ๊ณตํ•˜๋ฉด ์‚ฌ์šฉ์ž ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ , ์‹คํŒจํ•˜๋ฉด null์„ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
  1. ์„ธ์…˜ ์ƒ์„ฑ:
  • authorize ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ์‚ฌ์šฉ์ž ๊ฐ์ฒด๋Š” next-auth์— ์˜ํ•ด ์ž๋™์œผ๋กœ ์„ธ์…˜์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
    ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉ์ž ๊ฐ์ฒด์˜ ์ผ๋ถ€ ์ •๋ณด(์˜ˆ: id, name, email)๊ฐ€ ์„ธ์…˜์— ์ €์žฅ๋˜๋ฉฐ, ์ด๋ฅผ JWT์— ํฌํ•จํ•˜๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  1. ์„ธ์…˜์— ์‚ฌ์šฉ์ž ์ •๋ณด ์ €์žฅ:
  • callbacks ์˜ต์…˜์˜ jwt ๋ฐ session ์ฝœ๋ฐฑ์„ ํ†ตํ•ด ์„ธ์…˜์— ์ €์žฅ๋˜๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
const handler = NextAuth({
    <... ์œ„ ์ฝ”๋“œ ์ฐธ๊ณ >
    pages: {
        signIn: "/login"
    },
    session: {
        strategy: 'jwt',
        maxAge: 30 * 24 * 60 * 60,  //30์ผ๋™์•ˆ ์„ธ์…˜ ์œ ์ง€
        updateAge: 24 * 60 * 60,    // 24์‹œ๊ฐ„๋งˆ๋‹ค ์„ธ์…˜ ๊ฐฑ์‹ 
    },
    callbacks: {
        async jwt({ token, user}) {
            // ์‚ฌ์šฉ์ž ์ธ์ฆ ํ›„ JWT์— ์‚ฌ์šฉ์ž ์ •๋ณด ์ €์žฅ
            if (user) {
                token.email = user.email;
                token.name = user.name;
            }
            return token;
        },
        async session({ session, token }) {
            // ์„ธ์…˜ ๊ฐ์ฒด์— ์‚ฌ์šฉ์ž ์ •๋ณด ์ถ”๊ฐ€
            if (session.user) {
                session.user.email = token.email;
                session.user.name = token.name;
            }

            return session;
        }
    },

์ฃผ์š” ์ฝœ๋ฐฑ ์„ค๋ช…

  • jwt ์ฝœ๋ฐฑ:
    - ์‚ฌ์šฉ์ž ์ธ์ฆ ํ›„ JWT ํ† ํฐ์— ์ €์žฅํ•  ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์„ค์ •
    - ์‚ฌ์šฉ์ž ๊ฐ์ฒด๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ JWT ํ† ํฐ์— ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ถ”๊ฐ€

  • session ์ฝœ๋ฐฑ:
    - ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ์„ธ์…˜ ๊ฐ์ฒด์— ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ถ”๊ฐ€
    - jwt ์ฝœ๋ฐฑ์—์„œ ์„ค์ •ํ•œ ํ† ํฐ ์ •๋ณด๋ฅผ ์„ธ์…˜์— ๋ณต์‚ฌํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•จ

CredentialsProvider์—์„œ ์ธ์ฆ์— ์„ฑ๊ณตํ•˜๋ฉด ์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ ์ž๋™์œผ๋กœ ์„ธ์…˜์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
jwt์™€ session ์ฝœ๋ฐฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ธ์…˜์— ์ €์žฅ๋˜๋Š” ์ •๋ณด๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ํด๋ผ์ด์–ธํŠธ๋Š” useSession ํ›…์ด๋‚˜ getSession ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์„ธ์…˜ ์ •๋ณด๋ฅผ ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€