csurf 모듈
CSRF 공격을 방지하는 보안 모듈로 CSRF Secret와 CSRF Token을 만들어서 서로 매칭이 되는지 확인하는 기능을 제공합니다.
보통 CSRF Secret는 session/cookie에 저장하고 CSRF Token은 POST body에 저장합니다.
설치
1
npm i csurf
사용 방법
사용자가 티켓을 구매하는 과정으로 설명해보겠습니다.
사용자가 티켓을 구매하기 위해
https://ticket.com/purchase
로 접속을 합니다.서버에서 라우터에 GET 요청이 오면, 페이지와 CSRF 토큰을 클라이언트에 렌더링하여 제공합니다.
1 2 3
app.get("/purchase", csrfProtection, (req, res) => { res.render("index", { csrfToken: req.csrfToken() }); });
클라이언트 폼 태그에 토큰 정보를 저장합니다.
<!-- index.ejs --> <html> <body> <form action="/" method="POST"> // 서버에서 전달한 csrfToken을 input hidden 태그에 저장합니다. <input type="hidden" name="_csrf" value=csrfToken /> <input type="text" name="text" value="" /> <input type="submit" name="submit" /> </form> </body> </html>
POST Submit을 하게 되면, input 태그의 name인 _csrf 의 value(토큰)가 서버 라우터로 넘어가고 csrfProtection 미들웨어에서 검증합니다.
1 2 3
app.post("/purchase", csrfProtection, (req, res) => { res.send(req.body.text); });
전체 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const express = require("express");
const session = require("express-session");
const cookieParser = require("cookie-parser");
const csrf = require("csurf");
const app = express();
const csrfProtection = csrf({ cookie: true });
// 쿠키에 csrfSecret을 저장합니다.
// 만일 세션에 저장하고 싶다면 { cookie: false }로 설정합니다.
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session(sessionOption));
// cookie-parser나 express-session 미들웨어보다 아래에 위치해야합니다.
// csrfProtection 미들웨어를 라우터에 장착합니다.
app.get("/purchase", csrfProtection, (req, res) => {
// req.csrfToken()를 실행하여 csrf 토큰을 생성해서 저장 전달합니다.
res.render("index", { csrfToken: req.csrfToken() });
});
app.post("/purchase", csrfProtection, (req, res) => {
// 폼 submit이 POST로 오면 폼 태그 안에 있던 csrf 토큰 값을 csrfProtection 미들웨어에서 검증하고 next() 해줍니다.
res.send(req.body.text);
});
Ajax 요청에서 csrf 토큰
폼 태그가 아닌 ajax로 요청할때는 csrf 토큰을 저장할 곳을 폼이 아닌 보통 meta 태그에 저장하는 경우가 많습니다.
1
<meta name="csrf-token" content="xe1t9is6-Q1bcuuJ8G5rdTXWCRqzkSat7FUI" />
그 후 Ajax 요청을 할 때 요청 헤더에 CSRF-Token을 넣습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let token = document
.querySelector('meta[name="csrf-token"]')
.getAttribute("content");
fetch("/process", {
credentials: "same-origin",
headers: {
"CSRF-Token": token, // fetch 헤더에 'CSRF-Token'을 명시한다
},
method: "POST",
body: {
favoriteColor: "blue", // body 데이터
},
});
에러 처리
만일 토큰 인증에 실패하면 에러처리 미들웨어로 점프되며 err.code === 'EBADCSRFTOKEN'
객체가 담기게 됩니다.
이를 이용해 에러 처리를 할 수 있습니다.
1
2
3
4
5
6
7
// error handler
app.use(function (err, req, res, next) {
if (err.code !== "EBADCSRFTOKEN") return next(err); // 만일 토큰 에러가 아닌 다른 에러일경우 다른 에러처리 미들웨어로 보내고
// CSRF token errors 라면 다음과 같이 처리한다.
res.status(403).send("form tampered with");
});