NextJs 풀스택 구축기 2 로그인

OkGyoung·2023년 11월 8일
0

지난 포스트에 이어서 이번에는 직접적인 로그인 로직을 작성합니다.

"참고사항: 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;

이렇게 간단 로그인 과정이 끝났습니다.

자세한 사항은 링크을 통해서 더 알아보실수 있습니다.

profile
이유를 생각하는 개발자

0개의 댓글