Web Framework & Library/Next.js
[Next.js] NextAuth + sendmail 을 이용한 메일 인증 기반 회원가입 기능 구현 --- (2) NextAuth 설정
노호호
2025. 2. 4. 17:09
NextAuth.js는 Next.js 애플리케이션들을 위한 오픈소스 인증 솔루션이다.
Google, Kakao 등 많은 서드파티 서비스들을 쉽게 연계할 수 있게 해주고, Prisma 등 각종 ORM에 대한 지원도 잘해주는 툴이다.
나는 서드파티 fetch 없이 순수 이메일 가입 + Prisma + MySQL 형식으로 구현했다.
1. NextAuth 설치
아래 명령어를 사용해 NextAuth 및 Prisma 관련 패키지를 설치한다.
npm install next-auth @prisma/client @auth/prisma-adapter
npm install prisma --save-dev
2. app/api/auth/[...nextauth]/route.js 작성
그 다음엔 api/api/auth/[...nextauth]/ 디렉토리를 만든 후 route.js를 작성한다.
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { PrismaClient } from "@prisma/client";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import bcrypt from "bcrypt";
const prisma = new PrismaClient();
export const authOptions = {
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" }
},
authorize: async (credentials) => {
try {
console.log("Credentials received:", credentials);
const user = await prisma.user.findUnique({
where: { username: credentials.username }
});
console.log("User found:", user);
if (user) {
const isValidPassword = await bcrypt.compare(credentials.password, user.password);
console.log("Password valid:", isValidPassword);
console.log("user:", user);
if (isValidPassword) {
return { id: user.id, email: user.email, name: user.username };
}
}
return null;
} catch (error) {
console.error("Error in authorize function:", error);
return null;
}
}
})
],
adapter: PrismaAdapter(prisma),
secret: process.env.NEXTAUTH_SECRET,
session: {
jwt: true,
},
callbacks: {
async session({ session, token }) {
session.user.id = token.id;
session.user.username = token.username;
return session;
},
async jwt({ token, user }) {
if (user) {
token.id = user.id;
token.username = user.username;
console.log("JWT token after login:", token);
}
return token;
}
},
pages: {
signIn: '/login',
}
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
3. /login 페이지 연동
/app/login/page.js를 작성해서, 위에서 설정한 대로 로그인 페이지에서 signIn 이 가능하게 하면 된다.
"use client";
import { useState } from 'react';
import Link from 'next/link';
import { signIn } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import Header from '../../../components/Header';
import Footer from '../../../components/Footer';
const LoginComponent = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const handleSubmit = async (e) => {
e.preventDefault();
const result = await signIn("credentials", {
redirect: false,
username,
password
});
if (result.ok) {
if (username === 'admin') {
router.push('/admin-dashboard');
} else {
router.push('/');
}
} else {
setError("Login failed. Please check your username and password.");
}
};
return (
<div className="flex flex-col min-h-screen">
<Header />
<main className="flex-1 flex flex-col items-center justify-center bg-card">
<h1 className="text-4xl bricolage-grotesque mb-6">Login Page</h1>
<form onSubmit={handleSubmit} className="bg-white p-6 rounded-lg shadow-md w-full max-w-md">
<div className="mb-4">
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
Username
</label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="mt-1 block w-full px-3 py-2 border border-gray-300 text-black rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
required
/>
</div>
<div className="mb-6">
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
Password
</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="mt-1 block w-full px-3 py-2 border border-gray-300 text-black rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
required
/>
</div>
{error && <p className="text-red-500 text-sm mb-4">{error}</p>}
<button
type="submit"
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-black hover:bg-accent hover:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary"
>
Login
</button>
<div className="mt-4 flex justify-center space-x-2 text-primary">
<Link href="/signup">
<p className="text-lg font-medium hover:underline">Sign Up</p>
</Link>
<span>|</span>
<Link href="/request-recovery?type=username">
<p className="text-sm hover:underline">Find Username</p>
</Link>
<span>|</span>
<Link href="/request-recovery?type=password">
<p className="text-sm hover:underline">Find Password</p>
</Link>
</div>
</form>
</main>
<Footer />
</div>
);
};
export default LoginComponent;