[PWA] (33) pwd 암호화, cookie, JWT

Kimmy·2025년 6월 17일

PWA_PROJECT

목록 보기
45/47

role을 ADMIN으로 바꾸기

admin으로 가입했을때 role이 admin 으로 바뀌어야 하는데, USER로 저장되는(default)현상 -> ADMIN으로 나오도록 해결함

Prisma 에 enum 추가

enum Role 을 추가하고 USER 와 ADMIN 을 추가해준다.

enum Role {
  USER
  ADMIN
}

-> 나중에 ADMIN테이블을 따로 만들어야 하는지는 한번 찾아보기.

SignUpModal.js

formData.append("role", isAdmin ? "ADMIN" : "USER") ; 조건식 추가해줌

   const formData = new FormData();
    formData.append("userId", id);
    formData.append("pwd", password);
    formData.append("name", name);
    formData.append("phone", phone);
    formData.append("email", email);
    formData.append("isAdmin", String(isAdmin));
    formData.append("role", isAdmin ? "ADMIN" : "USER"); //추가
    if (isAdmin) {
      formData.append("adminPhoneNumber", adminPhone);
      formData.append("adminBusinessFile", businessFile);
    }

signup.js

백엔드(/api/signup)에서 fields.role을 값을 받아서 User 생성 시 data에 role: fields.role[0]을 추가하면, DB에 올바르게 저장됨.

const {
        userId,
        pwd,
        name,
        phone,
        email,
        isAdmin,
        adminPhoneNumber,
        role, //추가
      } = fields;

      let adminBusinessFileBuffer = null;

      if (files.adminBusinessFile) {
        const filePath = files.adminBusinessFile[0].filepath;
        adminBusinessFileBuffer = await readFile(filePath);
      }

      try {
        const newUser = await prisma.user.create({
          data: {
            userId: userId[0],
            pwd: pwd[0],
            name: name[0],
            phone: phone[0],
            email: email[0],
            role: role[0], //추가
            isAdmin: isAdmin[0] === "true",
            adminPhoneNumber: adminPhoneNumber?.[0] || null,
            adminBusinessFile: adminBusinessFileBuffer,
          },
        });

pwd 암호화하기

pwd가 MySQL에 평문으로 저장되고 있어서, 암호문으로 Hash처리해서 저장하는 방법 찾아보았다.
해시 처리는 일반적으로 bcrypt 모듈을 사용한다. 해시된 비밀번호는 MySQL Workbench에서도 평문으로 볼 수 없고, 보안이 유지된다.

순서

✅1. bcrypt 설치하기

npm install bcrypt

✅ 2. handler 함수에서 비밀번호 해시하기

SignUpModal.js

import bcrypt from "bcrypt"; // 추가
..
 try {
        // 비밀번호 암호화
        const hashedPwd = await bcrypt.hash(pwd[0], 10); // 10은 saltRounds

        const newUser = await prisma.user.create({
          data: {
            userId: userId[0],
            pwd: hashedPwd, // 암호화된 비밀번호 저장

✔️ saltRounds란?

bcrypt.hash(비밀번호, saltRounds)에서 saltRounds는 해시를 얼마나 복잡하게 만들지 결정하는 숫자.
salt는 해커가 미리 계산한 해시값(DB 탈취 후 비교하는 방식 등)으로부터 비밀번호를 보호하기 위한 랜덤한 추가값임.
saltRounds가 높을수록 더 복잡하고 안전한 해시가 만들어지지만, 계산 속도는 느려진다.
(pwd[0], 10)는 10번 반복해서 암호화하는 해시 복잡도 수준을 의미함.

✅ 3. 로그인 시에는 bcrypt.compare() 사용
bcrypt.compare() 로 비밀번호 비교하는 로직은 login API 파일에 구현해야 한다.
즉, /api/login.js에서 로그인 요청을 처리할 때 평문 비밀번호와 DB에 저장된 해시 비밀번호를 비교해주는 역할을 한다.

login.js

user.pwd !== pwd

위처럼, 평문을 비교하던 코드를 아래처럼,bcrypt로 비밀번호를 비교하도록 수정한다.

     const isMatch = await bcrypt.compare(pwd, user.pwd);

    if (!isMatch) {
      return res.status(401).json({ message: "아이디 또는 비밀번호가 틀렸습니다." });
    }

이렇게 수정하고 나면, 비번을 평문으로 저장했던 계정들은 비교가 불가해지면서, 로그인이 안된다. 계정삭제 필요 함.

🍑 요약

🔐 비밀번호 암호화는 회원가입 API (/api/signup)에서 bcrypt.hash() 사용

🔍 비밀번호 비교는 로그인 API (/api/login)에서 bcrypt.compare() 사용

로그인하고서 Header.js 에user.name불러오기

localStorage 대신 fetch 로 수정하기

계속해서 me.js에서 401에러가 나는바람에 로그인이 안됬다.
(지긋지긋한 에러표시)

 GET /api/me 401 in 17769ms

-> GET /api/me 200 in 24ms

🌷중요한점은 credentials: "include"를 꼭 써주어야한다.

TypeError: Cannot read properties of undefined (reading 'parse')
    at handler (src\pages\api\me.js:7:20)

약 두세시간동안 헤맨부분. 분명 cookie를 import 했는데도 불구하고, undefined라고 계~속 에러가 나는것이다.. 그래서 import 방식을 처음에는 모두 import 로 했다가, 또 모두 const 로 수정했는데도 같은 에러가 났다.

그래서 마지막으로 import 를 대부분 쓰고, cookie 만 const로 require 를 사용했더니 드디어 정상으로 작동했다.

import prisma from "../../../lib/prisma";
const cookie = require("cookie"); // ← 여기만 require로 변경
import jwt from "jsonwebtoken";
// import cookie from "cookie";

🔍 문제 원인 요약

Next.js의 API Routes (pages/api/…)는 Node.js 환경이기 때문에 cookie 패키지는 CommonJS 방식으로 require() 해야 정상 작동하는 경우가 있다.

import cookie from "cookie";

이 코드는 ES Modules 방식이다. 그러나 cookie 패키지는 기본적으로 CommonJS (CJS) 모듈 형식으로 배포되고 있어, 일부 환경에서는 import로 제대로 불러와지지 않는다.
그 결과, cookie 자체가 undefined로 로드되어 .parse를 호출할 수 없다는 에러가 발생했던것.

✅ 해결 방법

아래처럼 CommonJS 방식으로 불러오면 문제없이 동작한다:

const cookie = require("cookie");

⚠️ 왜 일부 import는 되는데 cookie는 안될까?
Next.js는 ES Module과 CommonJS를 혼용해서 지원하지만,
특정 라이브러리(특히 오래된 CommonJS 기반 라이브러리)는 import 방식에서 default export가 undefined로 로드될 수 있다.

예: cookie, jsonwebtoken, bcrypt 등

👉 이럴 땐 안전하게 require()를 쓰는 게 확실하다.

로그인 : localStorage -> fetch로 수정하기

 // useEffect(() => {
  //   const user = JSON.parse(localStorage.getItem("currentUser"));
  //   setCurrentUser(user);

  //   // 스토리지 변경 감지
  //   const handleStorageChange = () => {
  //     const updatedUser = JSON.parse(localStorage.getItem("currentUser"));
  //     setCurrentUser(updatedUser);
  //   };

  //   window.addEventListener("storage", handleStorageChange);
  //   return () => window.removeEventListener("storage", handleStorageChange);
  // }, []);

  useEffect(() => {
    const fetchUser = async () => {
      try {
        const res = await fetch("/api/me", { credentials: "include" });
        if (!res.ok) throw new Error("로그인 필요");

        const user = await res.json();
        setCurrentUser(user);
      } catch (error) {
        console.error("유저 정보 불러오기 실패", error);
        // setCurrentUser(null); // 로그인 안 한 상태
      }
    };

    fetchUser();
  }, []);

로그아웃 : localStorage -> fetch로 수정하기

 // const handleLogout = () => {
  //   localStorage.removeItem("currentUser");
  //   setCurrentUser(null);
  //   alert("로그아웃되었습니다.");
  // };

  const handleLogout = async () => {
    try {
      await fetch("/api/logout", { method: "POST" });
      setCurrentUser(null);
      alert("로그아웃되었습니다.");
    } catch (err) {
      alert("로그아웃 실패");
    }
  };
profile
바리바리 개바리 🌼

0개의 댓글