벌써 4번째 프로젝트!! 이번에 맡은 부분은 회원 인증 파트였는데 처음 써보는 Refresh Token, 그 Refresh Token을 Redis에 저장하는 과정을 기록해본다.
yarn add redis @redis/client jsonwebtoken cookie-parser
// ./auth-utils/redis.util.js
import redis from 'redis';
const redisClient = redis.createClient(process.env.REDIS_PORT);
redisClient.on('connect', () => console.log('Connected to Redis!'));
redisClient.on('error', (err) => console.log('Redis Client Error', err));
redisClient.connect();
export default redisClient;
jwt.sign()
을 사용한다.const token = jwt.sign({ user_id }, process.env.ACC_TOKEN_KEY);
console.log(token);
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo0LCJpYXQiOjE3MDI1NjAyNDIsImV4cCI6MTcwMjU2MDU0Mn0.Sr-6iTAeBuSzdHbPlj6MDzab7aTlB53oMZ8CwdksIaI
// 전체적인 login, logout 로직
login = async (req, res, next) => {
try {
const { email, password } = req.body;
const user = await this.authService.login(email, password);
// user{
// user{
// id: 1,
const user_id = user.user.id;
if (!user) {
throw error;
}
// Access Token 발급
const accessToken = jwt.sign(
{ user_id },
process.env.ACC_TOKEN_KEY, // 토큰 키
{
expiresIn: process.env.ACCESS_EXP_IN, // 만료 유효 기간
},
);
// Refresh Token 발급
const refreshToken = jwt.sign(
{ user_id },
process.env.REF_TOKEN_KEY,
{
expiresIn: process.env.REFRESH_EXP_IN,
},
);
// Access Token 쿠키 저장
res.cookie('accessToken', accessToken);
// Refresh Token 쿠키 저장
res.cookie('refreshToken', refreshToken);
// Refreshtoken redis 저장 (key, value)
redisClient.set(refreshToken, user_id);
return res.status(200).json({
message: '로그인 성공',
data: { accessToken, refreshToken },
});
} catch (error) {
console.log(error);
next(error);
}
};
logout = async (req, res, next) => {
try {
// Access Token 및 Refresh Token 변수 선언
const accessToken = req.cookies.accessToken;
const refreshToken = req.cookies.refreshToken;
// 쿠키에 담은 토큰들 삭제
res.clearCookie('accessToken');
res.clearCookie('refreshToken');
// Redis에서 토큰들 삭제
redisClient.del(refreshToken);
redisClient.del(accessToken);
return res.status(200).json({
message: '로그아웃 성공',
});
} catch (err) {
next(err);
}
};
// ./src/routes/user.router.js
import express from 'express';
import { UsersController } from '../modules/users/users.controller.js';
import authMiddleware from '../middlewares/auth.middleware.js';
const router = express.Router();
const usersController = new UsersController();
// 사용자 회원가입
router.post('/signup', usersController.signup);
// 사용자 정보
// authMiddleware 에서 token 인증을 해줄 것임!
router.get('/:id', authMiddleware, usersController.getUser);
export default router;
// ./src/middlewares/auth.middleware.js
import jwt from 'jsonwebtoken';
import { prisma } from './../utils/prisma/index.js';
import redisClient from '../../auth-utils/redis.util.js';
export default async (req, res, next) => {
try {
// cookie에 저장되어있는 토큰 값들을 가져온다.
const { accessToken, refreshToken } = req.cookies;
// Access Token 검증하기
const accessPayload = validToken(
accessToken,
process.env.ACC_TOKEN_KEY,
);
// Refresh Token 검증하기
const refreshPayload = validToken(
refreshToken,
process.env.REF_TOKEN_KEY,
);
// redis에 저장한 refresh token 가져오기
const redisRefreshToken = await redisClient.get(refreshToken);
// case 1) access token이 없을때
if (!accessToken) {
return res.status(401).json({ message: '다시 로그인 해주세요.' });
}
// case 2) accesstoken이 만료되고, refreshtoken은 있을때
if (!accessPayload) {
if (
refreshPayload &&
Number(redisRefreshToken) === jwt.decode(refreshToken).user_id
) {
const { user_id } = refreshPayload;
const newAccessToken = jwt.sign(
{ user_id },
process.env.ACC_TOKEN_KEY,
);
res.cookie('accessToken', newAccessToken);
}
return res
.status(200)
.json({ message: 'ACCESS TOKEN이 갱신 되었습니다.' });
}
// case 3) refreshtoken 없을때, accesstoken이 인증되었다면 새로운 refreshtoken 발급해주기
if (!refreshToken) {
if (accessPayload) {
const { user_id } = accessPayload;
const newRefreshToken = jwt.sign(
{ user_id },
process.env.REF_TOKEN_KEY,
);
res.cookie('refreshToken', newRefreshToken);
}
return res
.status(401)
.json({ message: 'REFRESH TOKEN이 존재하지 않습니다.' });
}
// case 4) redis에 refresh token 없을 때
if (!redisRefreshToken) {
return res
.status(401)
.json({ message: 'REFRESH TOKEN이 서버에 존재하지 않습니다.' });
}
// case 5) accesstoken은 있고, refreshtoken 만료되었을때
if (!refreshPayload) {
if (
accessPayload &&
Number(redisRefreshToken) === jwt.decode(accessToken).user_id
) {
const { user_id } = accessPayload;
const newRefreshToken = jwt.sign(
{ user_id },
process.env.REF_TOKEN_KEY,
);
res.cookie('refreshToken', newRefreshToken);
}
return res
.status(200)
.json({ message: 'REFRESH TOKEN이 갱신 되었습니다.' });
}
const { user_id } = accessPayload;
const user = await prisma.users.findUnique({ where: { id: user_id } });
res.locals.user = user;
next();
} catch (error) {
next(error);
}
};
// 토큰 검증 함수
function validToken(token, secretKey) {
try {
const payload = jwt.verify(token, secretKey);
return payload;
} catch (error) {
console.log(error);
}
}