지난 포스트에 이어서 이번에는 직접적인 로그인 로직을 작성합니다.
"참고사항: MongoDB를 로컬환경에서 이용시 replica 설정을 해주어야 합니다 쉬운 방법으로 MongoDB atlas를 이용하거나 mongo.cfg 파일 설정을 통해서 replica를 성정해야만 정상적으로 작동합니다."
Next.js 13 규칙으로 API 파일은 route.ts여야 합니다.
npm i -D @types/bcryptjs
먼저 비밀번호의 안전을 위해 bcrypt를 설치합니다.
npx prisma generate
그후 prisma 최신화해줍니다.
app/api/user/route.ts
import prisma from '@/app/lib/prisma'
import bcrypt from 'bcryptjs'
interface RequestBody {
name: string;
email: string;
password: string;
}
export async function POST(request: Request) {
const body: RequestBody = await request.json()
const user = await prisma.user.create({
data: {
name: body.name,
email: body.email,
password: await bcrypt.hash(body.password, 10),
},
})
const { password, ...result } = user
return new Response(JSON.stringify(result))
}
그 후 이렇게 회원가입 로직을 만들어 줍니다.
회원가입로직을 실행하면 POST /api/user
{
"name":"오동잎",
"email":"aaa@naver.com",
"password":"1q2w3e4r"
}
정상적으로 저장되고 비밀번호의 인코딩도 완료됩니다.
그렇다면 다음은 로그인입니다.
app/api/login/route.ts
import { prisma } from "@/app/utils/prisma";
import bcrypt from "bcryptjs";
interface RequestBody {
username: string;
password: string;
}
export async function POST(request: Request) {
const body: RequestBody = await request.json();
const user = await prisma.user.findFirst({
where: {
email: body.username,
},
});
if (user && (await bcrypt.compare(body.password, user.password))) {
const { password, ...userWithoutPass } = user;
return new Response(JSON.stringify(userWithoutPass));
} else return new Response(JSON.stringify(null));
}
로그인로직을 실행하면 POST /api/login
{
"email":"aaa@naver.com",
"password":"1q2w3e4r"
}
정상적으로 실행되는 모습을 볼 수 있습니다.
그럼 이렇게 만든 로직을 Next-Auth와 연결하겠습니다.
먼저 기존의 app/api/auth/[...nextauth]/route.ts을 열어줍니다.
import NextAuth from "next-auth/next";
import CredentialsProvider from "next-auth/providers/credentials";
const handler = NextAuth({
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
username: {
label: "이메일",
type: "text",
placeholder: "이메일 주소 입력 요망",
},
password: { label: "비밀번호", type: "password" },
},
async authorize(credentials, req) {
const res = await fetch(`${process.env.NEXTAUTH_URL}/api/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: credentials?.username,
password: credentials?.password,
}),
});
const user = await res.json();
console.log(user);
if (user) {
return user;
} else {
return null;
}
},
}),
],
});
export { handler as GET, handler as POST };
기존 const를 통해 임시로 저장한 id/pw대신 좀 전에 만든 로직을 추가합니다.
그럼 이번에는 로그인 component까지 만들고 이를 통해서 로그인하도록 하겠습니다.
app/component/login.tsx
"use client";
import { signIn, signOut } from "next-auth/react";
import React from "react";
function Login() {
return (
<div className="space-x-10">
<button
className="rounded-xl border bg-yellow-300 px-12 py-4"
onClick={() => signIn()}
>
LogIn
</button>
<button
className="rounded-xl border bg-red-300 px-12 py-4"
onClick={() => signOut()}
>
Log Out
</button>
</div>
);
}
export default Login;
이렇게 만든 로그인 로그아웃 버튼을 기본 page에 추가합니다.
app/page.tsx
import Login from "./component/login";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<h1 className="text-4xl font-semibold">NextAuth Tutorial</h1>
<Login />
</main>
);
}
이렇게 하면 홈페이지에 로그인 로그아웃 버튼인 생기고 이를 통해 바로 로그인과 로그아웃처리가 가능합니다.
이제 이렇게 만든 로그인 전체과정을 모든 곳에 적용하겠습니다.
app/components/Providers.tsx
"use client";
import { SessionProvider } from "next-auth/react";
import React, { ReactNode } from "react";
interface Props {
children: ReactNode;
}
function Providers({ children }: Props) {
return <SessionProvider>{children}</SessionProvider>;
}
export default Providers;
next-auth의 SessionProvider로 모든 컴포넌트를 감싸도록 하기 위해 새로운 Providers를 만들고 적용합니다.
/app/layout.tsx
import Providers from "./components/Providers";
import "./globals.css";
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<Providers>{children}</Providers>
</body>
</html>
);
}
다음으로 로그인 로그아웃을 분리하겠습니다. 로그인시에는 로그아웃이 보이고 비로그인시에는 로그인이 보이도록 해야 정상적인 로그인 버튼일 것 입니다.
/app/component/login.tsx
"use client";
import { signIn, signOut, useSession } from "next-auth/react";
import React from "react";
function Login() {
const { data: session } = useSession();
if (session && session.user) {
return (
<button
className="px-12 py-4 border rounded-xl bg-red-300"
onClick={() => signOut()}
>
{session.user.name}님 Log Out
</button>
);
}
return (
<button
className="px-12 py-4 border rounded-xl bg-yellow-300"
onClick={() => signIn()}
>
LogIn
</button>
);
}
export default Login;
이렇게 간단 로그인 과정이 끝났습니다.
자세한 사항은 링크을 통해서 더 알아보실수 있습니다.