유동
[Express] jwt로 로그인 유지시켜주기 본문
이번 글에서는 javascript + express환경에서 jwt를 어떻게 만들고.
어떻게 써먹는지 포스팅할것이다.
- 간단하게 설명하자면 요청을 보낸 사용자가 로그인한 사용자인지 상태를 체크할수있는거라고만 알아도 충분하다
먼저 jsonwebtoken 라이브러리를 설치해주자
npm install jsonwebtoken
일단 로그인이 성공하였을 경우 jwt를 발급해주니 로그인 api를 먼저 보겠다.
// 로그인 api
router.post("/login", async (req, res, next) => {
const { email, pw } = req.body;
const result = {
message: "",
data: {}
};
try {
validate(email, "email").checkInput().checkLength(1, ACCOUNT.MAX_EMAIL_LENGTH);
validate(pw, "pw").checkInput().checkLength(1, ACCOUNT.MAX_PW_LENGTH);
const sql = "SELECT id, pw FROM account_TB WHERE email = $1";
const params = [email];
const data = await pool.query(sql, params);
if (data.rowCount === 0) {
throw new BadRequestException("아이디 또는 비밀번호가 올바르지 않습니다");
}
const userData = data.rows[0];
const passwordMatch = bcryptUtil.compare(pw, userData.pw);
// 입력받은 pw와 암호화된 pw가 일치하지 않은 경우
if (!passwordMatch) {
throw new BadRequestException("아이디 또는 비밀번호가 올바르지 않습니다.");
}
// 로그인 검증 성공, 토큰발급해줘야지?
const accessToken = // 토큰 생성 후 변수에 담자
result.data = {
userId: userData.id
}
} catch (error) {
return next(error);
}
res.send(result);
});
로그인 사용자를 검증한 후 토큰을 만들어야한다. 나같은경우는 module폴더에 jwt를 생성하는 유틸함수를 작성했다.
- 토큰 발급에 대한 정보를 설정한다.
- .env파일에 jwt를 만들기위해 필요한 secretKey를 만들어놔야한다. 토큰 해독할때도 필요하다
// /src/module/jwt.js
const jwt = require("jsonwebtoken");
const { secretKey, accessTokenOption } = require("../../config/jwtSetting");
const userSign = (user) => {
// payload에는 서비스마다 다르겠지만 필요한 최소한의 정보만 담는다.
// 웬만하면 로그인한 사용자의 pk를 담는다.
const payload = {
id: user.id,
email: user.email
}
const secretKey = process.env.JWT_SECRET_KEY,
const accessTokenOption = {
"algorithm": "HS256", // 어떤 알고리즘을 사용할건지
"expiresIn": "1h", // 토큰의 유효기간
"issuer": "inko51366.com" // 토큰 발행자
}
return jwt.sign(payload, secretKey, accessTokenOption);
}
module.exports = {
userSign
};
이렇게 만들어주고 accessToken이 잘 출력되는지 확인해보자
- 인하대 학생 아닙니다
// 로그인 api
router.post("/login", async (req, res, next) => {
// ...생략
const accessToken = jwtUtil.userSign(userData);
console.log(accessToken);
} catch (error) {
return next(error);
}
res.send(result);
});
아주잘나온다
나온 jwt를 복사해서 jwt.io에 복사 붙여넣기 해보자
설정한 payload가 잘 나오는걸 볼수있다, (만료시간과 발급자) 이처럼 아무나 해독해서 볼수 있기때문에 민감한 정보는 payload에 담으면 안된다 (password, 개인정보 등)
마저 발급해보자
- 응답 본문(헤더)에 accessToken이라는 이름의 쿠키의 값에 jwt를 담아서 보내주자!이렇게하고 postman으로 테스트해보자
// 로그인 api
router.post("/login", async (req, res, next) => {
const { email, pw } = req.body;
// ...생략
const accessToken = jwtUtil.userSign(userData);
console.log(accessToken);
result.data = {
userId: userData.id
}
} catch (error) {
return next(error);
}
res.send(result);
});
잘 담긴걸 볼수있을것이다
자 이제 jwt를 발급해줬으니 로그인한 사용자만 이용할수 있는 api에서 로그인했는지 안했는지 테스트해보자
- 예를들어 회원가입 같은 api는 로그인하지 않은 사용자가 사용할수있을것임
회원가입 router.post("/", async (req, res, next) => { });
예제) 게시글 pk를 받아서 해당 게시글을 조회하는 api
// 특정 게시글 조회 api
// postId
// GET
router.get("/:postId", loginAuth, async (req, res, next) => {
const { postId } = req.params;
const result = {
data: null,
};
try {
validate(postId, "postId").checkInput().isNumber().checkLength(1, maxPostIdLength);
const sql = `SELECT
post_TB.*,
user_TB.name AS author_name,
user_TB.profile_img AS author_profile_img
FROM
post_TB
JOIN
user_TB
ON
post_TB.user_id = user_TB.id
WHERE
post_TB.id = $1`;
const params = [postId];
const data = await pool.query(sql, params)
if (data.rows.length !== 0) {
result.data = data.rows[0];
return res.send(result);
}
throw new NotFoundException("해당하는 페이지가 존재하지 않습니다");
} catch (error) {
next(error);
}
});
- 잘 보면 loginAuth라는 미들웨어를 특정 게시글 조회 api가 실행하기 전에 한번 실행된다. 저 loginAuth미들웨어에 가보자
const jwt = require("jsonwebtoken");
const { UnauthorizedException } = require("../module/customError");
const env = require('../config/env');
module.exports = (req, res, next) => {
// 쿠키에 담긴 토큰을 추출
const { accessToken } = req.cookies;
try {
req.decoded = jwt.verify(accessToken, env.JWT_SECRET_KEY);
return next();
} catch (error) {
return next(new UnauthorizedException("로그인 후 이용가능합니다"));
}
};
- jsonwebtoken 라이브러리에서 제공하는 verify 메서드를 통해 우리가 쿠키에 넣어줬던 jwt와 만들어줬던 secretkey를 가지고 토큰을 해독할 수 있다
req.decoded에 payload에 우리가 담아놓은 값들을 사용할 수 있다. 예시를 보자
예제) 게시글 pk, 수정하고싶은 내용들을 body로 받아서 해당하는 게시글을 수정시켜주는 api
고려해야 할 점
- 수정 api는 게시글을 작성 한 본인만 수정할수 있음.
- 어 우리는 게시글pk만 받는데 어떻게 본인인지 아닌지 확인하고 수정시켜줌?
이전에 loginAuth에서 토큰을 해독하고 req.decoded에다가 넣어줬으니
저 loginAuth미들웨어를 사용 한 api에서 req.decoded로 로그인 한 유저의 pk를 얻어낼 수 있다!
// 게시글 수정 api
// postId, title, content
// PUT
router.put("/", loginAuth, async (req, res, next) => {
const userId = req.decoded.id; // <= 1. 여기서 유저의 pk를 저장하고
const { postId, title, content, images } = req.body;
const result = {
isSuccess: false,
message: ""
};
try {
validate(postId, "postId").checkInput().isNumber().checkLength(1, maxPostIdLength);
validate(title, "title").checkInput().checkLength(1, maxPostTitleLength);
validate(content, "content").checkInput().checkLength(1, maxPostContentLength);
// sql에서 WHERE문으로 id와 user_id를 걸어주자
const updatePostSql = `UPDATE
post_TB
SET
title = $1,
content = $2,
image_key = $3
WHERE
user_id = $4
AND
id = $5`;
const updatePostParams = [title, content, images, userId, postId];
const updatePostData = await pool.query(updatePostSql, updatePostParams);
if (updatePostData.rowCount !== 0) {
result.isSuccess = true;
return res.send(result);
}
throw new BadRequestException("수정 실패, 해당하는 게시글이 존재하지 않습니다");
} catch (error) {
next(error);
}
});
이처럼 로그인이 필요한 api에 저렇게 토큰검증하는 미들웨어를 박아주면 요청 객체(req)에 해독한 정보들을 미리 담아둘수 있음
이후 필요할때 해독해서 쿵짝뽕짝 사용하면 된다
'node.js > ExpressJS' 카테고리의 다른 글
[Express] 비동기함수에 반복되는 try-catch 없애기 (0) | 2024.01.05 |
---|---|
[Express] 요청 객체를 dto로 변환하기 (0) | 2023.12.13 |
[Express] node-express에서 에러 핸들링 하기 (1) | 2023.10.24 |
[Express] 프론트에서 온 값을 검증하는 exception 모듈 만들기 (0) | 2023.08.02 |
[Express] Cannot read properties of undefined (0) | 2023.07.18 |