고양이와 코딩

[node] 비밀번호 암호화 - 회원가입, 로그인 구현하기 본문

node.js

[node] 비밀번호 암호화 - 회원가입, 로그인 구현하기

ovovvvvv 2024. 2. 24. 21:17
728x90

회원가입 로직

const join = (req, res) => {
  const { email, password } = req.body;

  const { hashedPassword, salt } = encryptPassword(password);

  let sql = `INSERT INTO users(email, password, salt) VALUES(?, ?, ?)`;

  console.log(hashedPassword);

  let values = [email, hashedPassword, salt];
  conn.query(sql, values, (err, results) => {
    if (err) {
      console.log(err);
      return res.status(StatusCodes.BAD_REQUEST).end();
    }
    if (results.affectedRows) {
      res.status(StatusCodes.CREATED).json(results);
    } else {
      return res.status(StatusCodes.BAD_REQUEST).end();
    }
  });
};

 

로그인 로직

const login = (req, res) => {
  const { email, password } = req.body;

  let sql = "SELECT * FROM users WHERE email = ?";

  conn.query(sql, email, (err, results) => {
    if (err) {
      console.error(err);
      return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
        error: "Internal Server Error",
      });
    }

    if (!results || results.length === 0) {
      return res.status(StatusCodes.UNAUTHORIZED).json({
        error: "Invalid credentials",
      });
    }

    const loginUser = results[0];
    const { hashedPassword } = encryptPasswordWithSalt(
      password,
      loginUser.salt
    );

    console.log(loginUser.password, hashedPassword);

    if (loginUser && loginUser.password === hashedPassword) {
      const token = jwt.sign(
        {
          id: loginUser.id,
          email: loginUser.email,
        },
        process.env.PRIVATE_KEY,
        {
          expiresIn: "100m",
          issuer: "yoojin",
        }
      );

      console.log(token);

      res.cookie("token", token, {
        httpOnly: true,
      });

      return res.status(StatusCodes.OK).json({ ...results[0], token: token });
    } else {
      return res.status(StatusCodes.UNAUTHORIZED).json({
        error: "Invalid credentials",
      });
    }
  });
};

 

프론트엔드단에서 회원가입 후 로그인을 실행하는 중 `Invalid credentials` 오류가 발생했습니다

 

제 코드에서는 Invalid credentials가 두 부분이어서 😅 콘솔을 찍어보며 확인 한 결과 if문 로직에 걸려서 내려오는 

코드 하단의 Invalid credentials 오류였습니다.

 

이 오류가 if(loginUser && loginUser.password === hashedPassword) 이 조건을 충족하지 못해서 터진 에러이기 때문에
console.log(loginUser.password,  hashedPassword) 값을 찍어서 두 값이 같은지 확인했습니다

당연히!!! 달랐고요 ㅎㅎ(당연함)

 

 

먼저 `utils/encryption.js`에 따로 암호화 하는 로직을 분리했었는데요, 

const crypto = require("crypto");

const encryptPassword = (password) => {
  // 임의의 salt 생성
  const salt = crypto.randomBytes(16).toString("base64");
  const hashedPassword = crypto
    .pbkdf2Sync(password, salt, 10000, 10, "sha512")
    .toString("base64");
  return { hashedPassword, salt };
};

module.exports = { encryptPassword };

회원가입 할때 받은 password를 encryptPassword 함수를 호출해서 암호화 하고 여기서 생성된 salt 값을 저장하지 않고

다시 로그인 할때 입력받은 password를 encrpytPassword 함수 내에서 임의로 새로 생성된 salt 값으로 암호화를 했기 때문에

결과적으로 같은 비밀번호인데 암호화 값이 달랐습니다.

 

이 문제를 해결하기 위해 encryptPassword 함수를 수정하려 했으나,, 이미 프론트엔드 강의를 들으며 api를 호출 할 때마다 터지는 에러에 정신을 놓아버리기 일보 직전이었기 때문에.... 

급한불만 끄자! 로 비슷한 모듈을 그냥 ctrl+c , ctrl+v로 만들어 버렸는데요

const encryptPasswordWithSalt = (password, salt) => {
  const hashedPassword = crypto
    .pbkdf2Sync(password, salt, 10000, 10, "sha512")
    .toString("base64");
  return { hashedPassword };
};

module.exports = { encryptPasswordWithSalt };

 

그게 이녀석입니다 ,,,

 

const crypto = require("crypto");

const encryptPassword = (password, salt = null) => {
  if (!salt) {
    // 임의의 salt 생성
    salt = crypto.randomBytes(16).toString("base64");
  }

  const hashedPassword = crypto
    .pbkdf2Sync(password, salt, 10000, 10, "sha512")
    .toString("base64");

  return { hashedPassword, salt };
};

module.exports = { encryptPassword };

이제 기존 encryptPassword 함수 하나로 salt 값이 없을때만 임의의 값을 생성하고, salt값이 있는 경우는 db에 저장된 값으로 

암호화하여 로그인에 성공하는걸 확인 할 수 있습니다!