JWT, NodeJS에서 jsonwebtoken 사용하기
1. install
npm i jsonwebtoken
npm i cookie-parser // cookie를 사용할 경우
npm i dotenv
2. .env 설정
ACCESS_TOKEN_SECRET=asd
REFRESH_TOKEN_SECRET=qwe
인증을 위한 access token, 유효기간 연장을 위한 refresh token을 사용한다.
각각 SECRET_KEY는 비밀스러워야하니 환경변수로 등록하고 .gitignore에 올려두자.
3. controller.js -> Login / Logout
export const postLogin = (req, res) => {
const {id, password} = req.body;
//id validation
if(id !== '1')
return res.status(400).render("login",{msg:"ID가 존재하지 않습니다."});
//password validation
//token 생성
const accessToken = jwt.sign({id}, process.env.ACCESS_TOKEN_SECRET, {
expiresIn: "1h",
});
// const refreshToken = jwt.sign({id}, process.env.REFRESH_TOKEN_SECRET, {
// expiresIn: "1d",
// });
res.cookie('accessToken', accessToken);
// res.cookie.refreshToken = refreshToken;
return res.redirect("/");
}
export const getLogout = (req, res) => {
res.clearCookie('accessToken');
return res.redirect("/");
};
아직 DB는 사용하지 않았다. 아이디가 1일 경우 로그인 성공.
refresh token은 사용하지 않았다. access token이 만료되면 다시 로그인을 시키도록 구현.
4. 검증을 위한 미들웨어
// middlewares.js
import jwt from "jsonwebtoken";
//jwt 유효성 검사
export const authenticateAccessToken = (req, res, next) =>{
if(!req.cookies.accessToken){
res.locals.loggedIn = Boolean(false);
return next();
}
const accessToken = req.cookies.accessToken;
// const refreshToken = res.cookie.refreshToken;
res.locals.loggedIn = Boolean(true);
if(!accessToken){
res.locals.loggedIn = Boolean(false);
return res.status(401).render("login", {msg:"잘못된 접근입니다."});
}
let ok;
try {
ok = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET);
} catch (error) {
res.locals.loggedIn = Boolean(false);
res.clearCookie('accessToken');
return res.status(401).render("login", {msg:"로그인 기한이 만료되었습니다."});
}
next();
};
res.cookie에 토큰을 저장하고, req.cookies에서 토큰을 불러온다.
pug view engine에서 로그인/로그아웃을 구현하도록 res.locals에도 boolean 값을 추가했다.
5. home.pug
body
header
include layouts/header.pug
main
block content
footer
include layouts/footer.pug
if msg
script.
alert(`#{msg}`)
에러메세지를 보기 위해 뷰엔진을 설정했다.
6. header.pug
div.nav-bar__home-side
ul.nav-bar__home-side-ul
li.nav-bar__default-li
a(href="/join") 회원가입
if !loggedIn
li.nav-bar__default-li
a(href="/login") 로그인
else
li.nav-bar__default-li
a(href="/logout") 로그아웃
로그인 여부에 따라 로그인 / 로그아웃을 보여주기 위한 뷰엔진을 구현했다.
7. server.js
import "dotenv/config";
import express from "express";
import rootRouter from "./routers/rootRouter";
import { authenticateAccessToken } from "./middlewares";
import cookieParser from "cookie-parser";
const PORT = 4000;
const app = express();
app.set("views", process.cwd()+"/src/views");
app.set('view engine', 'pug');
app.use("/public", express.static("public"));
app.use(cookieParser());
app.use(express.urlencoded({extended:true}));
app.use(authenticateAccessToken);
app.use("/", rootRouter);
app.listen(PORT, () => {
console.log(`✔️ Connect PORT: ${PORT}.`);
});
server는 코드가 짧으니 전체를 붙여넣었다.
jwt 유효성검사 middleware를 전부 사용하도록 구현.
결과
로그인 성공시 쿠키가 생성된다. 로그아웃시 쿠키가 삭제된다.
정리
jwt를 쿠키에 저장하냐, 로컬스토리지에 저장하냐에 따라 보안이 달라진다고 한다.
res.locals에 저장하면 편하지 않나? 라고 생각했지만, 페이지 이동간 전역변수로 활용이 불가능했다.
쿠키에 httpOnly로 저장하면 보안상 수월하다고 한다.
현재 나는 백엔드에서 jwt를 생성하고 프론트로 보낸 뒤 로컬스토리지에 저장하고있다.
별도의 공간은 차지하지 않지만, 크기가 커지면 트래픽이 발생하는 jwt,
크기가 커지면 DB를 잡아먹는 session.
두 인증방법 모두 소규모 프로젝트에서는 어떤걸 구현해도 상관없어 보인다.
개인적으로는 인스타그램이나 에버노트처럼 각각 계정을 관리를 할 수 있는 세션이 나아보인다.