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 설치 및 설정
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. 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', ], };
댓글 ( 0)
댓글 남기기