Access Token
은 몇 번 만들어봐서 어떤 흐름으로 코드가 짜여져야 하는지 이해하고 있었다 하지만 항상 마음 한 켠에는 Refresh Token
을 만들어 보고 싶은 생각을 가지고 있었는데 마침 이번 팀 프로젝트에서 내가 맡은 역할이 로그인 인증과 회원에 대한 대부분을 역할 맡았기 때문에 리프래쉬 토큰을 도전해볼 수 있었다.
엑세스토큰만 사용할 땐 로직이 그리 복잡하진 않았는데 똑같은 토큰이 하나 더 추가 됐을때는 로직이 상당히 복잡해진다는 느낌을 받을 수 있었다. 그렇기 때문에 미리 엑세스 토큰과 리프래시 토큰이 어떤 과정으로 되는지 그림으로 그려 이해를 하면서 그림을 따라서 코드를 작성하는 것을 추천한다. 아니면 코딩을 하다가 중간에 길을 잃게 되는 경우가 많았다
가장 유명하게 쓰이고 있는 엑세스토큰과 리프래시토큰의 로직 과정이다. 참고하도록 하자
이번 포스팅에서는
Redis
를 사용하지 않고Refresh Token
을 직접 서버DB에 저장해서 사용하는 방식으로 사용할 것 이다.
npm init -y
npm install express jsonwebtoken cookie-parser -S
각 프로젝트에 맞게 jwt와 cookieparser의 경로를 지정해주면 된다.
나는 app.js에서 가장 먼저 통과하고 있는 미들웨어 용도로 쓰이고 있기 떄문에 대부분의 기능을 여기서 불러주고 있다.
// app.js
import cookieParser from "cookie-parser"
app.use(cookieParser())
// auth.router.js
import jwt from "jsonwebtoken"
로그인이 성공했을 때 token
을 발행하기 때문에 로그인 api
에서 만들어 주도록 하자.
// routers/auth.Router.js
import jwt from "jsonwebtoken"
import { Router } from "express"
import db from "../models/index.js"
import { token_meddleware } from "../middlewares/token_middleware.js"
const { Users } = db;
const authRouter = Router();
// 로그인 API
authRouter.post("/auth/login", async (req, res, next) => {
const { email, password } = req.body;
const existUser = await Users.findOne({ where: { email } });
.
.
// 로그인 함수들
.
.
// accessToken 생성 함수
const id = existUser.id;
function createAccessToken(id) {
const accessToken = jwt.sign({ id: id }, (시크릿토큰키), { expiresIn: 시간 });
return accessToken;
}
// refreshToken 생성 함수
function createRefreshToken() {
const refreshToken = jwt.sign({}, (시크릿토큰키), { expiresIn: 시간});
return refreshToken;
}
const accessToken = createAccessToken(id);
const refreshToken = createRefreshToken();
// 생성한 토큰들을 쿠키에 저장
res.cookie("accessToken", `Bearer ${accessToken}`);
res.cookie("refreshToken", `Refresh ${refreshToken}`);
// 서버 db에 refreshToken 저장.
await Users.update(
{ refreshToken: refreshToken },
{ where: { email } },
);
return res.status(200).json({ success: true, message: "로그인 성공" });
// middelewares/token_middleware.js
import jwt from "jsonwebtoken";
import db from "../models/index.js";import
const { Users } = db;
const token_middleware = async (req, res, next) => {
const { accesstoken, refreshtoken } = req.cookies;
if (!accesstoken)
return res.status(400).json({
success: false,
errorMessage: "로그인 후 이용 가능합니다.",
});
const [tokenType, acctoken] = accesstoken.split(" ");
const [tokenTYPE, reftoken] = refreshtoken.split(" ");
// 쿠키 유효성 검증
const isRefreshTokenValidate = validateRefreshToken(reftoken);
const isAccessTokenValidate = validateAccessToken(acctoken);
// 리프래시 토큰 만료 시(재 로그인 유도)
if (!isRefreshTokenValidate)
return res.status(419).json({
success: false,
errorMessage:
"Refresh Token이 만료되었습니다. 재로그인이 필요합니다.",
});
// 액세스 토큰 만료 시 리프래시 토큰을 활용하여 재발급
if (!isAccessTokenValidate) {
const accessTokenId = await Users.findOne({
where: { refreshToken: reftoken },
});
if (!accessTokenId)
return res.status(419).json({
success: true,
errorMessage: "Access Token정보가 존재하지 않습니다.",
});
const newAccessToken = createAccessToken(accessTokenId.id);
res.cookie("accesstoken", `Bearer ${newAccessToken}`);
res.json({
success: true,
errorMessage: "Access Token을 재발급 성공!",
});
}
//--------------------------함수들------------------------------
// AccessToken 검증
function validateAccessToken(acctoken) {
try {
jwt.verify(acctoken, JWT_TOKENKEY_SECRET);
return true;
} catch (error) {
return false;
}
}
// RefreshToken 검증
function validateRefreshToken(reftoken) {
try {
jwt.verify(reftoken, JWT_TOKENKEY_SECRET);
return true;
} catch (error) {
return false;
}
}
function createAccessToken(id) {
const accessToken = jwt.sign({ id: id }, JWT_TOKENKEY_SECRET, {
expiresIn: JWT_ACCESS_TOKEN_EXPIRES_IN,
});
return accessToken;
}
//-------------------------------------------------------------
사실 그림에 나와있는 로직대로라면
accessToken이 만료 X
accessToken이 만료 O
일때로 나눠서 refreshToken을 검증하냐 안하냐로 나뉘게 되는대 내 로직에서는 상관없이 refreshToken을 같이 검증해주고 있는 불필요한 로직으로 짜여져 있다.
이점은 추 후 다시 생각해보면서 한 번 도전 해 볼 예정이다.
리프레시토큰의 권위자 리스펙~