React

 

 

NextAuth.js

https://next-auth.js.org/getting-started/example

 

 

 

몽고 DB  설정

 

1) next.config.js

const { PHASE_DEVELOPMENT_SERVER } = require('next/constants');

module.exports = (phase) => {
  if (phase === PHASE_DEVELOPMENT_SERVER) {
    return {
      env: {
        mongodb_username: "macaronics",
        mongodb_password: "jVPUh2t43T34324jjP5MuQ5",
        mongodb_clustername: "mongo-test.test1234.mongodb.net",
        mongodb_database: "test123",
        appName: "my-site-dev"
      },
    };
  }
 
  return {
    env: {
        mongodb_username: "macaronics",
        mongodb_password: "jVPUh2t43T34324jjP5MuQ5",
        mongodb_clustername: "mongo-test.test1234.mongodb.net",
        mongodb_database: "test123",
        appName: "macaronics-blog"
    },
  };
};

 

 

2)lib/db.js 설정

import { MongoClient } from "mongodb";

async function connectToDatabase() {
  let client;
  let clientPromise;
  
  if (!clientPromise) {
    const connectionString = `mongodb+srv://${process.env.mongodb_username}:${process.env.mongodb_password}@${process.env.mongodb_clustername}/${process.env.mongodb_database}?retryWrites=true&w=majority&appName=${process.env.appName}`;
    client = new MongoClient(connectionString);
    clientPromise = client.connect();
  }

  await clientPromise;

  return {
    client,
    db: client.db(),
  };
}

export { connectToDatabase };

 

 

3)사용 contact.js

import { connectToDatabase } from '../../lib/db';


async function handler(req, res) {
  if (req.method === "POST") {
    const { email, name, message } = req.body;

    //console.log(email, name, message);

    if (
      !email ||
      !email.includes("@") ||
      !name ||
      name.trim() === "" ||
      !message ||
      message.trim() === ""
    ) {
      return res.status(422).json({ message: "Invalid input" });
    }

    const newMessage ={
        email,
        name, message
    }

   
    const dbConnection = await connectToDatabase();
    let client = dbConnection.client;
    let db = dbConnection.db;

    try {
        const result=await db.collection('messages').insertOne(newMessage);    
        newMessage.id=result.insertedId;

    } catch (error) {
        res.status(500).json({ message: error.message});
        return;
    }finally{
      if(client){
        await client.close();
        client = null; // 클라이언트 객체 초기화
      }        
    }
    
    //console.log("success======>",newMessage);
    return res.status(200).json({ message: "Success", insertMessage:newMessage });
  }


}



export default handler;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1. NextAuth.js를 이용한 인증 구현 요약 

 

 

nextAuth3 버전

NextAuth.js는 Next.js 애플리케이션에 인증 기능을 쉽게 추가할 수 있는 라이브러리입니다. 다양한 인증 방법을 지원하며, 이를 통해 사용자 로그인 기능을 구현할 수 있습니다.

 

NextAuth.js 설치 및 설정

  1. NextAuth.js 설치:

    • 터미널에서 개발 서버를 종료한 후 다음 명령어를 입력하여 NextAuth.js 패키지를 설치합니다.

 

 

npm install next-auth

 

API 라우트 생성:

  • NextAuth.js 설정을 위한 API 라우트를 생성합니다. pages/api/auth/[...nextauth].js 파일을 생성하고 다음과 같이 설정합니다.

 

import NextAuth from "next-auth";
import Providers from "next-auth/providers";

export default NextAuth({
  providers: [
    Providers.Credentials({
      name: "Credentials",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" }
      },
      authorize: async (credentials) => {
        // 사용자 인증 로직 구현
        const user = { id: 1, name: "John Doe", email: "john@example.com" }; // 예시 사용자
        if (user) {
          return user;
        } else {
          return null;
        }
      }
    })
  ],
  // 추가 설정 옵션
});

 

 

사용자 로그인 및 인증 확인:

  • NextAuth.js는 서버 사이드와 클라이언트 사이드 모두에서 인증을 확인할 수 있습니다.
  • 서버 사이드 예제

 

import { getSession } from "next-auth/client";

export async function getServerSideProps(context) {
  const session = await getSession(context);
  if (!session) {
    return {
      redirect: {
        destination: "/api/auth/signin",
        permanent: false
      }
    };
  }
  return {
    props: { session }
  };
}

 

클라이언트 사이드 예제

 

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

export default function Page() {
  const [session, loading] = useSession();
  if (loading) return <p>Loading...</p>;
  if (!session) return <p>You are not logged in</p>;
  return <p>Welcome, {session.user.name}</p>;
}

 

사용자 인터페이스 업데이트:

  • 사용자 인증 상태에 따라 다른 인터페이스를 표시할 수 있습니다.
  • 로그인 상태에 따라 표시되는 컴포넌트 예제:

 

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

export default function NavBar() {
  const [session, loading] = useSession();
  return (
    <nav>
      {session ? (
        <>
          <p>Welcome, {session.user.name}</p>
          <button onClick={() => signOut()}>Sign out</button>
        </>
      ) : (
        <button onClick={() => signIn()}>Sign in</button>
      )}
    </nav>
  );
}

 

사용자 관리:

  • NextAuth.js는 사용자 생성을 관리하지 않으므로 자체 회원가입 API 라우트와 사용자 인증 로직을 구현해야 합니다.

 

// pages/api/auth/signup.js
import { hash } from "bcryptjs";

export default async function handler(req, res) {
  if (req.method === "POST") {
    const { email, password } = req.body;
    const hashedPassword = await hash(password, 12);
    // 데이터베이스에 사용자 저장 로직 구현
    res.status(201).json({ message: "User created" });
  } else {
    res.status(405).json({ message: "Method not allowed" });
  }
}

 

위의 단계를 통해 Next.js 애플리케이션에 NextAuth.js를 이용한 인증 기능을 추가할 수 있습니다.

이를 통해 다양한 인증 방법을 지원하고, 사용자 로그인 상태에 따라 다른 인터페이스를 표시하는 등의 작업을 쉽게 구현할 수 있습니다.

 

 

 

NextAuth4 버전

 

 

NextAuth.js를 사용하여 로그인 인증을 구현하려면, Next.js 애플리케이션에서 NextAuth를 설정하고, 제공되는 인증 공급자(예: Google, GitHub 등)를 사용하여

로그인 프로세스를 관리할 수 있습니다. 아래는 NextAuth.js 버전 4를 사용하여 로그인 인증을 설정하는 예시입니다.

1) 프로젝트 설정: Next.js 프로젝트를 초기화하고 필요한 패키지를 설치합니다.

npx create-next-app@latest my-next-app
cd my-next-app
npm install next-auth@latest

 

2) API 라우트 설정: NextAuth를 위한 API 라우트를 설정합니다. pages/api/auth/[...nextauth].js 파일을 생성합니다.

import NextAuth from "next-auth";
import GithubProvider from "next-auth/providers/github";
import CredentialsProvider from 'next-auth/providers/credentials';
import { verifyPassword } from '../../../lib/auth';
import { connectToDatabase } from '../../../lib/db';

export const authOptions = {

  //jwt 사용 설정
  session: {
    jwt: true,
    // // Defaults to `session.maxAge`.
    //maxAge: 60 * 60 * 24 * 30,
  },

  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),

    CredentialsProvider({
      async authorize(credentials) {
        const { email, password } = credentials;

        const dbConnection = await connectToDatabase();
        let client = dbConnection.client;
        let db = dbConnection.db;

        try {
          const user = await db.collection("users").findOne({ email: email });
          if (!user) {
            throw new Error("해당 이메일 정보를 가진 유저가 존재하지 않습니다.");
          }

          const isValid = await verifyPassword(password, user.hashedPassword);

          if (!isValid) {
            throw new Error("비밀번호가 유효하지 않습니다.");
          }

          return { 
            name: user.email, 
            email: user.email ,
            id:user._id,
            role: user.role            
          };

        } catch (error) {
          console.error("Authorization error: ", error.message);
          // 오류 메시지를 반환
          throw new Error(error.message);
        } finally {
          if (client) {
            await client.close();
          }
        }
      },
    }),
  ],

  callbacks: {
    async session({ session, token }) {
      if (token.email) {
        session.user.id = token.id;
        session.user.email = token.email;
        session.user.role = token.role;  // 사용자 역할 추가

        console.log("callbacks callbacks  session  : ",session);
      }
      return session;
    },
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id;
        token.email = user.email;
        token.role = user.role;  // 사용자 역할 추가
        console.log(" token  : ",token);
      }
      return token;
    },
  },

  // /auth/signin 경로에 로그인 페이지가 있다고 가정하면, NextAuth.js는 인증이 필요한 사용자를 이 경로로 리디렉션합니다. 
  // 이 경로에 로그인 폼을 만들고, 사용자가 로그인을 시도할 때 해당 페이지에서 인증을 처리하게 됩니다.
  pages: {
    signIn: '/auth',
  },
};

export default NextAuth(authOptions);

 

3) 비밀번호 검증 함수 추가:

lib/auth.js 파일에 비밀번호 검증 함수를 추가합니다.

import { hash, compare } from 'bcryptjs';

export async function hashPassword(password) {
  const hashedPassword = await hash(password, 12);
  return hashedPassword;
}

export async function verifyPassword(password, hashedPassword) {
  const isValid = await compare(password, hashedPassword);
  return isValid;
}

 

 

4) 회원가입

  pages/api/auth/ signup.js

import { hashPassword } from "../../../lib/auth";
import { connectToDatabase } from "../../../lib/db";

async function handler(req, res) {
  if (req.method === "POST") {
    const data = req.body;
    const { email, password } = data;

    console.log(email, password);

    // 이메일과 비밀번호 유효성 검사
    if (!email || !email.includes("@") || !password || password.trim().length < 4) {
      res.status(422).json({ message: "유효하지 않은 이메일 또는 비밀번호입니다." });
      return;
    }

    let client;
    let db;
    try {
      // 데이터베이스에 연결
      const dbConnection = await connectToDatabase();
      client = dbConnection.client;
      db = dbConnection.db;

      // 기존 사용자인지 확인
      const existingUser = await db.collection("users").findOne({ email: email });
      if (existingUser) {
        res.status(422).json({ message: "이미 회원 가입처리된 이메일입니다." });
        return;
      }

      // 비밀번호 해시 처리
      const hashedPassword = await hashPassword(password);

      let role="user";
      if(email==="admin@gmail.com"){
        role="admin"; // admin일 경우 role="admin"로 설정합니다.
      }


      // 새로운 사용자 삽입
      const result = await db.collection("users").insertOne(
        { email, hashedPassword ,
          role: role,  // 기본 역할 설정
       });
       
      res.status(201).json({ message: "성공", data: result.insertedId });
    } catch (error) {
      console.error(error); // 에러 로그 출력
      res.status(500).json({ message: "서버 내부 오류" });
    } finally {
      // 데이터베이스 연결을 닫습니다.
      if (client) {
        await client.close();
      }
    }
  } else {
    res.status(405).json({ message: "허용되지 않은 메소드입니다." });
  }
}

export default handler;

 

 

5) 로그인  폼:

auth/auth-form.js

import { useRef, useState } from "react";
import { signIn } from 'next-auth/react';
import classes from "./auth-form.module.css";
import { useRouter } from "next/router";

async function createUser(email, password) {
  const res = await fetch(`/api/auth/signup`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      email,
      password,
    }),
  });

  const data = await res.json();

  if (!res.ok) {
    throw new Error(data.message || "Something went wrong!");
  }

  return data;
}

async function signInHandler(email, password) {
  const result = await signIn('credentials', {
    redirect: false,
    email,
    password,
  });
  return result;
}

function AuthForm() {
  const [isLogin, setIsLogin] = useState(true);
  const emailInputRef = useRef();
  const passwordInputRef = useRef();
  const router=useRouter();


  function switchAuthModeHandler() {
    setIsLogin((prevState) => !prevState);
  }

  async function submitHandler(event) {
    event.preventDefault();
    const emailInput = emailInputRef.current.value;
    const passwordInput = passwordInputRef.current.value;
  
    if (isLogin) {
      const result=await signInHandler(emailInput, passwordInput);
      emailInputRef.current.value="";
      passwordInputRef.current.value="";
      if (result.error) {        
        // 로그인 실패
        alert('로그인 실패: ' + result.error);
      } else {
        // 로그인 성공
        alert('로그인 성공');
        router.push("/");
      }
    } else {

      try {
        const result = await createUser(emailInput, passwordInput);
        emailInputRef.current.value="";
        passwordInputRef.current.value="";
        setIsLogin(true);
        alert("회원 가입을 축하 합니다.");       
        
      } catch (error) {
        alert(error.message);
      }
    }
  }

  return (
    <section className={classes.auth}>
      <h1>{isLogin ? "로그인" : "회원가입"}</h1>
      <form onSubmit={submitHandler}>
        <div className={classes.control}>
          <label htmlFor="email">이메일</label>
          <input type="email" id="email" required ref={emailInputRef} />
        </div>
        <div className={classes.control}>
          <label htmlFor="password">비밀번호</label>
          <input
            type="password"
            id="password"
            required
            ref={passwordInputRef}
          />
        </div>
        <div className={classes.actions}>
          <button>{isLogin ? "로그인" : "회원가입"}</button>
          <button
            type="button"
            className={classes.toggle}
            onClick={switchAuthModeHandler}
          >
            {isLogin ? "계정생성" : "로그인"}
          </button>
        </div>
      </form>
    </section>
  );
}

export default AuthForm;

 

6) 데이터베이스 연결 설정:

MongoDB 연결을 위한 lib/db.js 파일을 설정합니다.

import { MongoClient } from "mongodb";

async function connectToDatabase() {
  let client;
  let clientPromise;
  
  if (!clientPromise) {
    const connectionString = `mongodb+srv://${process.env.mongodb_username}:${process.env.mongodb_password}@${process.env.mongodb_clustername}/${process.env.mongodb_database}`;
    client = new MongoClient(connectionString);
    clientPromise = client.connect();
  }

  await clientPromise;

  return {
    client,
    db: client.db(),
  };
}

export { connectToDatabase };

 

 

 

7)환경 변수 설정

.env.local  또는 next.config.js 파일을 생성하고 필요한 환경 변수를 설정합니다.

next.config.js

const { PHASE_DEVELOPMENT_SERVER } = require('next/constants');

module.exports = (phase) => {
  if (phase === PHASE_DEVELOPMENT_SERVER) {
    return {
      env: {
        mongodb_username: "macaronics",
        mongodb_password: "23213",
        mongodb_clustername: "mongo-test.test.mongodb.net",
        mongodb_database: "nextjsEx3Auth",
        appName: "my-site-dev",

        GITHUB_ID:"test1",
        GITHUB_SECRET:'12324'
      },
    };
  }
 
  return {
    env: {
        mongodb_username: "macaronics",
        mongodb_password: "1232",
        mongodb_clustername: "mongo-test.test.mongodb.net",
        mongodb_database: "nextjsEx3Auth",
        appName: "test-blog",

        GITHUB_ID:"test1",
        GITHUB_SECRET:'12324'
    },
  };
};

 

 

 

 

8) 로그아웃

 

_app.js

import Layout from "../components/layout/layout";
import "../styles/globals.css";
import { SessionProvider } from "next-auth/react";

function MyApp({ Component, pageProps: { session, ...pageProps } }) {
  return (
    <SessionProvider session={session}>
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </SessionProvider>
  );
}

export default MyApp;

 

 

main-navigation.js  로그아웃 버튼을 포함하는 페이지를 설정합니다.

// components/main-navigation.js
import Link from "next/link";
import { useSession, signOut } from "next-auth/react";
import classes from "./main-navigation.module.css";

function MainNavigation() {
  const { data: session } = useSession();

  let isLoginContent;
  
  if (session) {
    isLoginContent = (
      <>
        <li>
          <span className="text-white">{session.user.email} 님</span>
        </li>
        <li>
          <button onClick={() => signOut()}>로그아웃</button>
        </li>
      </>
    );
  } else {
    isLoginContent = (
      <>
        <li>
          <Link href="/auth" legacyBehavior>
            <a>로그인</a>
          </Link>
        </li>
      </>
    );
  }

  return (
    <header className={classes.header}>
      <Link href="/" legacyBehavior>
        <a>
          <div className={classes.logo}>Next Auth</div>
        </a>
      </Link>
      <nav>
        <ul>
          <li>
            <Link href="/profile" legacyBehavior>
              <a>프로파일</a>
            </Link>
          </li>
          {isLoginContent}
        </ul>
      </nav>
    </header>
  );
}

export default MainNavigation;

 

 

 

9) NextAuth 설정 확인: 이제 애플리케이션을 실행하고 NextAuth 설정이 제대로 작동하는지 확인합니다.

npm run dev

브라우저에서 http://localhost:3000으로 이동하여 로그인 및 로그아웃 기능을 테스트할 수 있습니다.

이 기본 예제는 Google  , GITHUB 인증 공급자를 사용합니다. 다른 인증 공급자를 사용하려면 Providers 객체에 해당 공급자를 추가하면 됩니다. 또한, 데이터베이스 설정 및 세션 관리 등 추가적인 옵션도 NextAuth 문서를 참고하여 설정할 수 있습니다.

 

 

 

 

 

 

 

 

2.  페이지 접근 제어를 설정

 

  • a 페이지: 모든 사람이 접근 가능
  • b 페이지: 로그인한 사람만 접근 가능
  • c 페이지: 로그인한 사람 중 admin만 접근 가능

 

 

A 개별적 설정 방법

 

1) a 페이지 설정

pages/a.js 파일을 생성합니다. 이 페이지는 모든 사람이 접근할 수 있습니다

export default function PageA() {
  return (
    <div>
      <h1>Page A</h1>
      <p>This page is accessible by everyone.</p>
    </div>
  );
}

 

2) b 페이지 설정 (로그인한 사람만 접근 가능)

pages/b.js 파일을 생성합니다. 이 페이지는 로그인한 사람만 접근할 수 있습니다.

NextAuth.js의 getSession을 사용하여 세션을 확인합니다.

 

import { getSession } from 'next-auth/react';

export default function PageB() {
  return (
    <div>
      <h1>Page B</h1>
      <p>This page is only accessible by logged-in users.</p>
    </div>
  );
}

export async function getServerSideProps(context) {
  const session = await getSession(context);
  
  if (!session) {
    return {
      redirect: {
        destination: '/auth/signin',
        permanent: false,
      },
    };
  }

  return {
    props: { session },
  };
}

 

 

3) c 페이지 설정 (admin만 접근 가능)

pages/c.js 파일을 생성합니다. 이 페이지는 로그인한 admin만 접근할 수 있습니다.

사용자 역할을 확인하기 위해 NextAuth.js의 getSession을 사용합니다.

 

import { getSession } from 'next-auth/react';

export default function PageC() {
  return (
    <div>
      <h1>Page C</h1>
      <p>This page is only accessible by admin users.</p>
    </div>
  );
}

export async function getServerSideProps(context) {
  const session = await getSession(context);
  
  if (!session || session.user.role !== 'admin') {
    return {
      redirect: {
        destination: '/auth/signin',
        permanent: false,
      },
    };
  }

  return {
    props: { session },
  };
}

 

 

 

 

 

 

 

B. _middleware.js를 이용하여 일괄적 설정 방법

 

참고  : https://nextjs.org/docs/app/building-your-application/routing/middleware

1)next.config.js 파일에 NEXTAUTH_SECRET 생성

암호 생성 사이트  : https://ko.pw-gen.com/

 

 

const { PHASE_DEVELOPMENT_SERVER } = require('next/constants');

//암호 생성 사이트  : NEXTAUTH_SECRET
//https://ko.pw-gen.com/
module.exports = (phase) => {
  if (phase === PHASE_DEVELOPMENT_SERVER) {
    return {
      env: {
        mongodb_username: "유저네임",
        mongodb_password: "비번",
        mongodb_clustername: "clustername",
        mongodb_database: "nextjsEx3Auth",
        appName: "my-site-dev",

        NEXTAUTH_SECRET:"%vFT=rII0T/)*&$-maTE1)elt~9nFs=6QIbMnq(2C/-=5t/K&)pmJ1iPe~2R3BAiH!TWLbn3URn+9onIBLkRPB$ZodGCbYv51iX~",
        GITHUB_ID:"test1",
        GITHUB_SECRET:'12324'
      },
    };
  }
 
  return {
    env: {
        mongodb_username: "유저네임",
        mongodb_password: "비번",
        mongodb_clustername: "clustername",
        mongodb_database: "nextjsEx3Auth",
        appName: "macaronics-blog",

        NEXTAUTH_SECRET:"%vFT=rII0T/)*&$-maTE1)elt~9nFs=6QIbMnq(2C/-=5t/K&)pmJ1iPe~2R3BAiH!TWLbn3URn+9onIBLkRPB$ZodGCbYv51iX~",
        GITHUB_ID:"test1",
        GITHUB_SECRET:'12324'
    },
  };
};

 

 

 

2) [...nextauth].js 에서  secret 를 추가 한다.

[...nextauth].js

 

~


export const authOptions = {

 
  secret: process.env.NEXTAUTH_SECRET,
 //jwt 사용 설정
  session: {
    jwt: true,
    // // Defaults to `session.maxAge`.
    //maxAge: 60 * 60 * 24 * 30,
    secret: process.env.NEXTAUTH_SECRET,
  },


~


};

export default NextAuth(authOptions);

 

 

3) root   middleware.ts  이름으로 파일 생성한다.

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { getToken } from 'next-auth/jwt';

export async function middleware(req: NextRequest) {
  //console.log("Request URL: ", req.url);
  //console.log("Request Cookies: ", req.cookies);

  const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
  //console.log("middleware=  token :", token);


  
  const { pathname } = req.nextUrl;

  if (pathname.startsWith('/profile')|| pathname.startsWith('/b')) {
    if (!token) {
      return NextResponse.redirect(new URL('/auth', req.url));
    }
  }

  if (pathname.startsWith('/admin')|| pathname.startsWith('/c')) {
    if (!token || token.role !== 'admin') {
      return NextResponse.redirect(new URL('/', req.url));
    }
  }

  return NextResponse.next();
}


//보호하고 싶은 경로들을 설정합니다.
export const config = {
  matcher: ['/profile/:path*', '/admin/:path*' , '/b/:path*', '/c/:path*']
};

 

 

 

 

a페이지

export default function PageA() {
    return (
      <div>
        <h1>Page A</h1>
        <p>이 페이지는 모든 사람이 접근할 수 있습니다.</p>
      </div>
    );
  }

 

b페이지

export default function PageB() {
  return (
    <div>
      <h1>Page B</h1>
      <p>b 페이지 설정 (로그인한 사람만 접근 가능).</p>
    </div>
  );
}

 

c 페이지

export default function PageC() {
  return (
    <div>
      <h1>Page C</h1>
      <p>이 페이지는 로그인한 admin만 접근할 수 있습니다.</p>
    </div>
  );
}

 

admin 페이지

export default function AdminPage() {
  return (
    <div>
      <h1>Page AdminPage</h1>
      <p>이 페이지는 로그인한 admin만 접근할 수 있습니다.</p>
    </div>
  );
}

 

 

 

 

 

 

 

 

 

2.JWT(JSON Web Token) 방식

 

JWT 인증을 위한 NextAuth.js 설정

  1. 1. NextAuth.js 설치:

    • 터미널에서 개발 서버를 종료한 후 다음 명령어를 입력하여 NextAuth.js 패키지를 설치합니다.

 

npm install next-auth

 

2. JWT 시크릿 키 생성:

  • JWT 시크릿 키는 토큰을 서명하고 검증하는 데 사용됩니다. 프로젝트 루트 디렉토리의 .env.local 파일에 추가합니다.

 

NEXTAUTH_SECRET=your_secret_key

 

3.NextAuth.js 설정 파일 생성:

  • pages/api/auth/[...nextauth].js 파일을 생성하고 JWT 방식을 설정합니다.
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
import jwt from "jsonwebtoken";

export default NextAuth({
  providers: [
    Providers.Credentials({
      name: "Credentials",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" }
      },
      authorize: async (credentials) => {
        // 사용자 인증 로직 구현
        const user = { id: 1, name: "John Doe", email: "john@example.com" }; // 예시 사용자
        if (user) {
          return user;
        } else {
          return null;
        }
      }
    })
  ],
  session: {
    jwt: true,
  },
  jwt: {
    secret: process.env.NEXTAUTH_SECRET,
  },
  callbacks: {
    async jwt(token, user) {
      if (user) {
        token.id = user.id;
      }
      return token;
    },
    async session(session, token) {
      session.user.id = token.id;
      return session;
    },
  }
});

 

4.사용자 로그인 및 인증 확인:

  • 서버 사이드에서 인증 확인 예제
import { getSession } from "next-auth/react";

export async function getServerSideProps(context) {
  const session = await getSession(context);
  if (!session) {
    return {
      redirect: {
        destination: "/api/auth/signin",
        permanent: false
      }
    };
  }
  return {
    props: { session }
  };
}

 

클라이언트 사이드에서 인증 확인 예제

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

export default function Page() {
  const { data: session, status } = useSession();
  if (status === "loading") return <p>Loading...</p>;
  if (!session) return <p>You are not logged in</p>;
  return <p>Welcome, {session.user.name}</p>;
}

 

5.사용자 인터페이스 업데이트:

  • 로그인 상태에 따라 다른 인터페이스를 표시하는 컴포넌트 예제

 

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

export default function NavBar() {
  const { data: session, status } = useSession();
  if (status === "loading") return null;
  return (
    <nav>
      {session ? (
        <>
          <p>Welcome, {session.user.name}</p>
          <button onClick={() => signOut()}>Sign out</button>
        </>
      ) : (
        <button onClick={() => signIn()}>Sign in</button>
      )}
    </nav>
  );
}

 

6.사용자 관리:

  • NextAuth.js는 사용자 생성을 관리하지 않으므로 자체 회원가입 API 라우트와 사용자 인증 로직을 구현해야 합니다.
  • 예: 사용자 등록 API 라우트
// pages/api/auth/signup.js
import { hash } from "bcryptjs";

export default async function handler(req, res) {
  if (req.method === "POST") {
    const { email, password } = req.body;
    const hashedPassword = await hash(password, 12);
    // 데이터베이스에 사용자 저장 로직 구현
    res.status(201).json({ message: "User created" });
  } else {
    res.status(405).json({ message: "Method not allowed" });
  }
}

 

위의 단계를 통해 JWT 방식을 사용하여 Next.js 애플리케이션에 NextAuth.js를 이용한 인증 기능을 추가할 수 있습니다.

이를 통해 다양한 인증 방법을 지원하고, 사용자 로그인 상태에 따라 다른 인터페이스를 표시하는 등의 작업을 쉽게 구현할 수 있습니다.

 

 

 

 

 

 

 

 

 

 

 

3.middleware.ts 설정

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { getToken } from 'next-auth/jwt';
import fetch from 'isomorphic-unfetch';

interface TokenType {
  memberId: number;
  userId:string;
  name: string;
  email: string;
  sub: string;
  role: string;
  accessToken: string;
  refreshToken: string;
  iat: number;
  exp: number;
  jti: string;
}

// 타입 가드 함수
function isTokenType(token: any): token is TokenType {
  return (
    token &&
    typeof token.memberId === 'number' &&
    typeof token.userId === 'string' &&
    typeof token.name === 'string' &&
    typeof token.email === 'string' &&
    typeof token.sub === 'string' &&
    typeof token.role === 'string' &&
    typeof token.accessToken === 'string' &&
    typeof token.refreshToken === 'string' &&
    typeof token.iat === 'number' &&
    typeof token.exp === 'number' &&
    typeof token.jti === 'string'
  );
}

export async function middleware(req: NextRequest) {
  const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
  const { pathname } = req.nextUrl;

  // 보호하고 싶은 경로들을 설정합니다.
  const protectedPaths = [
    '/profile',
    '/admin',
    '/boards/write',
    '/boards/update',
  ];

  if (protectedPaths.some(path => pathname.startsWith(path))) {
    console.log(" 1protectedPaths  체크  :", token);
    if (!token || !isTokenType(token)) {
      return NextResponse.redirect(new URL('/auth', req.url));
    }

    console.log(" 2protectedPaths  체크");


    // 토큰이 있고 만료 시간을 체크하여 갱신이 필요한지 확인합니다.
    const tokenExpiration = token.exp * 1000; // 토큰 만료 시간 (Unix 타임스탬프를 밀리초로 변환)
    const now = Date.now();
    const threshold = 5 * 60 * 1000; // 5분 (토큰 갱신 임계시간)

    // 토큰 만료 5분 이내에 요청이 들어오면 갱신 시도
    if (tokenExpiration - now < threshold) {
      try {
        const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/auth/refreshToken`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${token.accessToken}`,
          },
          // 갱신 토큰 요청 시 필요한 데이터 (예: refresh token 등)
          body: JSON.stringify({ refreshToken: token.refreshToken }),
        });

        if (!response.ok) {
          throw new Error('토큰 갱신 실패');
        }

        const data = await response.json();

        console.log("토큰 갱신 성공: ", data);

        // 갱신된 토큰을 설정합니다.
        token.accessToken = data.accessToken;
        token.exp = data.exp;

      } catch (error) {
        console.error('토큰 갱신 실패:', error);
        // 갱신 실패 시 로그아웃 처리는 클라이언트 측에서 수행되어야 합니다.
      }
    }
  }

  // 관리자 페이지 접근 권한 확인
  if (pathname.startsWith('/admin')) {
    if (!token || !isTokenType(token) || token.role.toUpperCase() !== 'ADMIN') {
      return NextResponse.redirect(new URL('/', req.url));
    }
  }

  // 로그인 페이지 및 회원가입 페이지
  if (pathname.startsWith('/auth') || pathname.startsWith('/signup')) {
    if (token && isTokenType(token)) {
      return NextResponse.redirect(new URL('/', req.url));
    }
  }

  return NextResponse.next();
}

// 보호하고 싶은 경로들을 설정합니다.
export const config = {
  matcher: [
    '/profile/:path*',
    '/auth',
    '/admin/:path*',
    '/boards/write',
    '/boards/update',
  ],
};


 

 

 

 

 

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

경험이란 인간이 자신들의 잘못에 붙이는 별명이다. -오스카 와일드

댓글 ( 0)

댓글 남기기

작성