Express.js에서 JWT 처음 적용해보기

야생피카츄·2024년 1월 10일

회원인증을 유지하는 방법엔 크게 session방식과 token방식 두가지가 있습니다. session은 서버에서 인증정보를 관리하는 방식이라면 token은 서버에서 토큰을 한 번 발행해 유저에게 전달해주면 다시 유저 요청에서 token을 검사해 인증하는 방식입니다. 조금 더 서버자원 관리에 유리한 token방식을 채택하였고, 표준으로 사용되는 JWT를 적용해 보기로 하였습니다.

🧐JWT란?

JSON Web Token
두 개체간의 정보를 JSON객체를 사용하여 안전하게 전달하기 위한 웹 표준입니다.

서명을 통해 키를 보유하고 있는 당사자만 서명한 사람임을 인증할 수 있습니다.

JWT 구조

다음과 같은 세 부분으로 구성되며, .으로 구분됩니다.

  • header (토큰 타입, 사용된 서명 알고리즘)
  • payload (발행자, 만료시간 등)
  • signature (서명 알고리즘으로 헤더, 페이로드, secret 정보를 가져와 생성)

따라서 일반적으로 xxxxx.yyyyy.zzzzz와 같은 형태를 가집니다. jwt.io.Debugger 에서 JWT를 디코딩하여 확인, 생성해볼 수 있습니다.

😎구현

토큰 발행

먼저 로그인시 토큰을 발행하는 부분을 구현해 보겠습니다. 사용자를 확인 후 토큰을 발행하는 코드입니다.

router.post("/login", async (req, res) => {
  try {
    const key = process.env.JWT_SECRET;
    const { email, password } = req.body;

    const user = await User.findOne({
      email: email
    });

    if (!user) {
      return res.status(404).json({ message: "사용자를 찾을 수 없습니다." });
    }

    // 비밀번호 해싱된 값과 비교
    const isPasswordValid = await bcrypt.compare(password, user.password);

    if (!isPasswordValid) {
      return res
        .status(401)
        .json({ code: 401, message: "비밀번호가 일치하지 않습니다." });
    }

    const token = jwt.sign(
      {
        type: "JWT",
        email: email,
      },
      key,
      {
        expiresIn: "24h",
        issuer: "토큰발급자",
      }
    );

    return res.status(200).json({
      code: 200,
      message: "토큰이 생성되었습니다.",
      token: token,
    });
  } catch (error) {
    return res.status(419).json({
      code: 419,
      message: error,
    });
  }
});

토큰 검증

토큰을 검증하는 미들웨어입니다. Bearer 스키마를 사용하기 때문에 split를 하는 부분이 있습니다. 이에 관해선 조금 뒤에 추가적으로 설명하겠습니다.

const jwt = require("jsonwebtoken");
require("dotenv").config();

const authenticateToken = (req, res, next) => {
  const token =
    req.headers.authorization && req.headers.authorization.split(" ")[1];
  const key = process.env.JWT_SECRET;

  if (token == null) {
    return res.status(401).json({
      code: 401,
      message: "토큰이 없습니다.",
    });
  }

  jwt.verify(token, key, (err, decoded) => {
    if (err) {
      if (err.name === "TokenExpiredError") {
        return res.status(403).json({
          code: 403,
          message: "토큰이 만료되었습니다.",
        });
      } else {
        return res.status(403).json({
          code: 403,
          message: "토큰이 유효하지 않습니다.",
        });
      }
    }
    req.decoded = decoded;
    next(); // 다음 미들웨어 또는 핸들러로 제어를 넘깁니다.
  });
};

module.exports = authenticateToken;

검증 확인

authenticateToken을 통해 제대로 토큰을 검증하고 있는지 확인해봅니다.

router.get("/payload", authenticateToken, async (req, res) => {
  console.log(req.decoded);
  const { email } = req.decoded;

  return res.status(200).json({
    code: 200,
    message: "토큰이 정상입니다.",
    data: {
      email: email,
    },
  });
});

Request

로그인을 통해 받은 토큰을 헤더의 Authorization에 포함시키면 됩니다. 공식문서에 Bearer 스키마를 사용하도록 되어있어 토큰 앞에 Bearer가 포함되어 있습니다. 이렇게 하면 클라이언트에서 서버로 요청시 암호화 되기 때문에 더 안전하게 전달할 수 있다고 합니다.

Authorization: Bearer <token>

참조

https://jwt.io/introduction
https://velog.io/@sjy0917/Express%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-JWT

profile
각성개발자

0개의 댓글