=> 사용자의 권한이 확인되었을 경우 사용자를 인증하는 용도로 발급한다
jwt 도 Access token 중 하나이다
-> 모든 정보를 관리하는 것이 아니라 특정 사용자가 access token 을 발급받을 수 있게 하기 위한 용도로 사용된다
( 토큰이 탈취당할 경우 피해를 최소화 시키기 위함 )
OTP 와 같이 인증 시간이 짧고 주기적으로 재발급하기 때문에 피해가 최소화
예를 들어 auth-middleware 로 검증을 한다고 했을 경우 access token 과 refresh token 을 한번에 검증하는 행위는 위험할 수 있다.
( access token 과 refresh token 이 한번에 탈취당할 수 있다는 보안적인 이유 )
따라서 auth-middleware 에서는 access-token 을 검증하고 만료되었을 경우 refresh-token 을 검증하는 다른 api 를 설계해서 진행한다
그러면 refresh-token 검증 api 에서 refresh-token verify 가 유효한 경우 access-token 을 재발급한 후 기존의 api 를 다시 요청한다.
만료되었을 경우 다시 로그인을 해야하기 때문에 로그인 페이지로 이동하는 로직으로 설계하였다.
refresh-token 의 경우 userId 를 저장한 상태로 redis 에 key:value 값으로 저장되어 관리하기 용이하게 설정하였다. rtVerify api 를 수행할 때 프론트엔드에게 userId 값을 받아와 그것을 refresh-token 에 저장하는 방식으로 구현하였다.
const jwt = require("jsonwebtoken");
require("dotenv").config();
const env = process.env;
const { Users } = require("../models");
module.exports = async (req, res, next) => {
const accessToken = req.headers.accesstoken
? req.headers.accesstoken
: req.cookies.accessToken;
// access token 이 존재하지 않는 경우 로그인 페이지로 이동
if (!accessToken) {
throw new Error("400/Access Token이 존재하지 않습니다.");
}
const [authType, authToken] = (accessToken ?? "").split(" ");
if (authType !== "Bearer" || !authToken) {
throw new Error("419/Access Token이 유효하지 않습니다.");
}
const userId = validateAccessToken(authToken);
if (!userId) {
throw new Error("403/Access Token이 만료되었습니다.");
}
const user = await Users.findOne({
where: { userId: userId },
});
res.locals.user = user;
// console.log(`${userId}의 Payload 를 가진 Token이 성공적으로 인증되었습니다.`);
next();
};
function validateAccessToken(accessToken) {
try {
const { userId } = jwt.verify(accessToken, `${env.SECRET_KEY}`); // JWT를 검증합니다.
return userId;
} catch (error) {
return false;
}
}
( /api/users/rtVefiry )
verifyRefreshToken = async (req, res, next) => {
try {
const refreshToken = req.headers.refreshtoken;
const userId = req.headers.userid;
await this.authService.verifyRefreshToken(refreshToken);
const newAccessToken = await this.authService.createAccessTokenById(
userId,
);
console.log("accessToken 을 다시 발급하였습니다!");
res.cookie("accessToken", `Bearer ${newAccessToken}`);
res.status(200).json({ accessToken: `Bearer ${newAccessToken}` });
} catch (error) {
error.failedApi = "refreshToken 검증";
throw error;
}
};
}
verifyRefreshToken = async (refreshToken) => {
const [authType, authToken] = (refreshToken ?? "").split(" ");
if (authType !== "Bearer" || !authToken) {
throw new Error("419/refreshToken의 형식이 일치하지 않습니다.");
}
const refreshTokenInfo = await this.redisClientRepository.getRefreshToken(
authToken,
);
if (!refreshTokenInfo) {
throw new Error("419/refreshToken의 정보가 서버에 존재하지 않습니다.");
}
try {
jwt.verify(authToken, `${env.SECRET_KEY}`);
return true;
} catch (error) {
throw new Error("419/refreshToken이 유효하지 않습니다.");
}
};
}
## redisClientRepository
const redis = require("redis");
const sequelize = require("sequelize");
require("dotenv").config();
class RedisClientRepository {
constructor() {
this.redisClient = redis.createClient({
url: `redis://${process.env.REDIS_USERNAME}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/0`,
legacyMode: true,
});
this.redisConnected = false;
}
initialize = async () => {
this.redisClient.on("connect", () => {
this.redisConnected = true;
console.info("Redis connected!");
});
this.redisClient.on("error", (error) => {
console.error("Redis Client Error", error);
});
if (!this.redisConnected) {
this.redisClient.connect().then();
}
};
setRefreshToken = async (refreshToken, email) => {
await this.initialize();
await this.redisClient.v4.set(refreshToken, email);
};
getRefreshToken = async (refreshToken) => {
await this.initialize();
return await this.redisClient.v4.get(refreshToken);
};
deleteRefreshToken = async (refreshToken) => {
await this.initialize();
await this.redisClient.v4.del(refreshToken);
};
}
module.exports = RedisClientRepository;