jwt 토큰 인증
- 로그인 할 때 발급해 cookie에 넘겨준 jwt 토큰을 header에서 꺼내 사용한다.
jwt api 테스트
- res.cookie에 담아보내기 ➡️ response Headers의 Set-Cookie에 담긴다.
- 지난 쿠키값 그대로 받아 다음 api 실행 시 req.cookie에 그대로 씀
// GET + "/jwt" : 토큰 발행
app.get('/jwt', (req, res) => {
var token = jwt.sign({
username : 'kim'
}, process.env.PRIVATE_KEY,
{
expiresIn: '1m',
issuer: 'admin'
});
// 토큰 쿠키에 담기
res.cookie("jwt", token, {
httpOnly: true
});
console.log(token);
res.send("토큰 발행 완료!");
})
res.cookie ➡️ response Headers의 Set-Cookie에 담는다.


authorization 받아보기
- cookie에 담아 보내준 토큰을 복호화 시 header의 authorization 키에 넣는다.
- req.headers["authorization"]으로 해당 토큰을 읽는다.
- 복호화 한다.
// GET + "/jwt/decoded" : 토큰을 검증
app.get('/jwt/decoded', (req, res) => {
let receivedJwt = req.headers["authorization"];
console.log(receivedJwt);
var decoded = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
res.send(decoded);
})

좋아요 추가 api에 jwt 구현
- 로그인 시 토큰 발행 : 페이로드에 db에서 가져온 id와 email 넣기
- 토큰 복호화해서 id가져오기 : decodedJwt.id를 user_id 대신 values에 넣어주면 된다.
// token 발급 / 유효기간 설정
const token = jwt.sign({
id : loginUser.id,
email : loginUser.email
}, process.env.PRIVATE_KEY, {
expiresIn : '1m', // 유효시간
issuer : 'yj' // 발행인
});
let receivedJwt= req.headers["authorization"];
console.log("received jwt: ", receivedJwt);
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
// 좋아요 추가
let sql = `INSERT INTO likes (user_id, liked_book_id) VALUES (?, ?)`;
let values = [decodedJwt.id, book_id];
좋아요 취소 API 구현, user_id 꺼내기 모듈화
- 좋아요 취소 API에도 인증 복호화 코드를 작성하려니 좋아요 추가 API 안에서의 코드와 겹친다.
➡️ 함수로 인증 복호화 코드를 모듈화하여 재사용
const removeLike = (req, res)=>{
let book_id = req.params.id;
// 인증 복호화
let authorization = ensureAuth(req);
let sql = `DELETE FROM likes WHERE user_id = ? AND liked_book_id = ?`;
let values = [authorization.id, book_id];
});
.
.
.
// 인증 복호화
function ensureAuth(req){
let receivedJwt= req.headers["authorization"];
console.log("received jwt: ", receivedJwt);
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
return decodedJwt;
}
장바구니 API에서 jwt 구현
- 장바구니 API에서 jwt 인증을 구현하려는데 다음과 같은 문제가 발생했다.
1) jwt expired
2) ensureAuth가 파일마다 반복된다.
3) 장바구니 아이템 목록 조회 = 내 장바구니 보기
➡️ user_id로 회원의 장바구니를 전부 보여줘야 하는데 현재는 selected 없이 보내면 안된다.
➡️선택한 장바구니 목록 조회와 분리 필요
- 위 문제를 하나씩 해결해보고자 한다.
jwt expired 에러 컨트롤
- jwt 유효기간 지나면 현재 500 서버 에러가 나면서 서버가 종료된다.
- 이때, 예외(개발자가 생각하지 못한 에러) 처리를 통해 유효기간이 지났을 경우,
res ➡️ '로그인(인증) 세션(로그인 유지 상태)이 만료되었습니다.' 메세지를 출력하도록 해주어야 한다.
jwt 예외 처리 종류
1) jwt.TokenExpiredError
- 유효기간이 지난 토큰 = 만료된 토큰
2) jwt.JsonWebTokenError
- 문제 있는 토큰
try ... catch
- 개발자가 예상하지 못한 에러를 처리하는 문법
- 실수, 사용자가 입력을 잘못한 것, 디비가 응답을 잘못한 것...etc ➡️ 프로그램에서 발생하는 수많은 에러들
- if/else를 쓰기에는 수많은 에러의 경우의 수를 파악하기 어렵다
➡️ 실수1, 실수2... 비슷한 분류들끼리 묶어서 누군가.. 따로 관리를 해주면 좋겠다.
// if ~ else 구문
코드 A;
if (A에서 발생한 실수1){
}else if(A에서 발생한 실수2){
}
// try ~ catch 구문
try{
코드 A;
} catch (err) {
// 에러 처리
}
- try 구문의 코드를 실행하다가 에러가 발생하면, try 코드를 멈추고 catch로 err와 함께 바로 빠져나간다.
- try 구문에서 어떤 에러가 발생해도 if문 분기 처리를 해주던 내용들이 알아서 catch에 잡힌다.
ex. SyntaxError, TypeError => 에러 객체
에러 객체
- 자바스크립트가 많이 발생하는 에러를 토대로 내장 에러 객체를 만들었다.
- JWT 모듈에서 제공하는 에러 객체 + 직접 만들 수도 있다.
console.log(err.name); // 에러 객체의 이름
console.log(err.message); // 에러 객체 메세지
// (출력 예시)
TokenExpiredError
jwt expired
throw 연산자
- 에러를 발생시키는 연산자 ➡️ throw 에러 객체
ex. throw new SyntaxError();
let error = new Error("대장 에러 객체");
let syntaxError = new SyntaxError("구문 에러 객체");
let referencedError = new ReferenceError("대입 에러 객체");
console.log(error.name);
console.log(error.message);
console.log(syntaxError.name);
console.log(syntaxError.message);
console.log(referencedError.name);
console.log(referencedError.message);
Error
대장 에러 객체
SyntaxError
구문 에러 객체
ReferenceError
대입 에러 객체
- if는 구문을 다 읽는데 try catch는 문제가 발생했을 때 throw를 사용하면 안 읽고 catch로 넘겨줌
let string = '{ "num1": 1}'
try{
// username;
let json = JSON.parse(string);
if(!json.name){
throw new SyntaxError("입력 값에 이름이 없습니다.");
}else{
console.log(json.name); // js 입장에선 에러가 아니지만, 우리 입장에선 에러! = 입력값이 잘못된 에러
}
let name = json.name;
console.log(json.name);
}catch(err){
// 먼저 잡힌 에러를 먼저 반환한다.
console.log(err.name);
console.log(err.message);
}
SyntaxError
입력 값에 이름이 없습니다.
TokenExpiredError 발생 시 에러 처리
// 인증 복호화
function ensureAuth(req, res){
try {
let receivedJwt= req.headers["authorization"];
console.log("received jwt: ", receivedJwt);
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
return decodedJwt;
} catch (err) {
console.log(err.name);
console.log(err.message);
return res.status(StatusCodes.UNAUTHORIZED).json({
"message" : "로그인 세션이 만료되었습니다. 다시 로그인하세요."
});
}
}
- 해당 코드에서 유효기간 만료가 되면 catch 구문에 err.name = TokenExpiredError, err.message = jwt expired 가 출력된다.
- 유효기간 만료 시 res.json으로 message를 리턴해줬다.
res를 두 번 보내면 생기는 일
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
➡️ res 응답 리턴을 두 번 보내게 돼서 생기는 에러
const addLike = (req, res)=>{
let book_id = req.params.id;
// 인증 복호화
let authorization = ensureAuth(req, res);
// 좋아요 추가
let sql = `INSERT INTO likes (user_id, liked_book_id) VALUES (?, ?)`;
let values = [authorization.id, book_id];
// INSERT 쿼리문
conn.query(sql, values,
function (err, results) {
if(err){
console.log(err)
return res.status(StatusCodes.BAD_REQUEST).end(); // Bad Request(400)
}
return res.status(StatusCodes.CREATED).json(results); // 201
}
);
}
- ensureAuth에서 res.json()을 리턴해주면 addLike에서는 res.json()을 두번 리턴해주게 된다.
- ensureAuth에서는 err만 리턴해준다.
// 인증 복호화
function ensureAuth(req, res){
try {
let receivedJwt= req.headers["authorization"];
console.log("received jwt: ", receivedJwt);
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
return decodedJwt;
} catch (err) {
console.log(err.name);
console.log(err.message);
return err;
}
}
instanceOf 사용하기
- instanceof 연산자는 인스턴스(객체)가 해당 클래스의 인스턴스인지를 판단하는 연산자이다.
- ensureAuth가 리턴해준 err 안에 TokenExpired 에러가 있다면 분기 처리로 res.status.json()을 리턴할 수 있다.
- 마찬가지로 JsonWebTokenError(잘못된 토큰 입력) 에러 발생 시 BAD_REQUEST로 에러 메세지를 리턴한다.
- 분기 처리를 통해 return res.json()은 한 번만 해줄 수 있다.
// 인증 복호화
let authorization = ensureAuth(req, res);
// instanceof : 객체가 어떤 클래스의 인스턴스인지 알아내기 위해 사용
if(authorization instanceof jwt.TokenExpiredError){
return res.status(StatusCodes.UNAUTHORIZED).json({
"message" : "로그인 세션이 만료되었습니다. 다시 로그인하세요."
});
}else if(authorization instanceof jwt.JsonWebTokenError){
return res.status(StatusCodes.BAD_REQUEST).json({
"message" : "잘못된 토큰입니다."
});
}
else{
let sql = `INSERT INTO cartItems (book_id, quantity, user_id) VALUES (?, ?, ?)`;
let values = [book_id, quantity, authorization.id];
'프로젝트 > 도서구매사이트' 카테고리의 다른 글
| 1030 회원 인증 모듈화 적용, 전체 도서 pagination json 추가, 리팩토링 항목 (0) | 2025.10.30 |
|---|---|
| 1028 conn.query()의 비동기 처리, query 와 execute 차이, 주문 완료한 아이템 장바구니에서 삭제 (0) | 2025.10.28 |
| 1027 비동기 처리 방식 종류 - promise, async/await (0) | 2025.10.26 |
| 1024 주문 API 구현 - insertId 활용, 다중 인서트 구현 (0) | 2025.10.23 |
| 1023 장바구니 API 구현 - 선택한 장바구니 목록 조회 IN [배열] 적용 (0) | 2025.10.22 |