회원인증을 유지하는 방법엔 크게 session방식과 token방식 두가지가 있습니다. session은 서버에서 인증정보를 관리하는 방식이라면 token은 서버에서 토큰을 한 번 발행해 유저에게 전달해주면 다시 유저 요청에서 token을 검사해 인증하는 방식입니다. 조금 더 서버자원 관리에 유리한 token방식을 채택하였고, 표준으로 사용되는 JWT를 적용해 보기로 하였습니다.
JSON Web Token
두 개체간의 정보를 JSON객체를 사용하여 안전하게 전달하기 위한 웹 표준입니다.
서명을 통해 키를 보유하고 있는 당사자만 서명한 사람임을 인증할 수 있습니다.
다음과 같은 세 부분으로 구성되며, .으로 구분됩니다.
따라서 일반적으로 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,
},
});
});
로그인을 통해 받은 토큰을 헤더의 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