프로필

프로필 사진
Popomon
Frontend Developer
(2020/12 ~)

    카테고리

    포스트

    [Node/Rest API] ACCESS_TOKEN / CSRF_TOKEN 처리

    2020. 11. 9. 17:17

    꿈가게: To Do List - iOS

    꿈가게: To Do List - Android

    CSRF 토큰 생성

    많은 프로젝트가 서버측의 csurf 라이브러리를 사용하여 CSRF 처리를 합니다. 훌륭한 라이브러리지만 개발자가 작동방식을 잘못 이해하는 경우가 많습니다.

     

    이 라이브러리가 어떻게 사용되어야 하는지 알아보겠습니다. 세션 미들웨어 없이 cookie를 사용하여 csurf를 사용할 것입니다.

     

    var cookieParser = require('cookie-parser');
    var csrf = require('csurf');
    var express = require('express');
    
    // 쿠키 사용 설정
    var app = express();
    app.use(cookieParser());
    
    // CSRF 미들웨어 인스턴스 생성
    var csrfProtection = csrf({ cookie: true });
    
    app.get('/', csrfProtection, function(req, res) {
      
      // 모든 요청에 CSRF 토큰을 저장
      res.cookie('XSRF-TOKEN', req.csrfToken(), {
        expires: new Date(Date.now() + 3 * 3600000) // 3시간 동안 유효
      });
      
      // 데이터 리턴
      res.json({});
      
    });

     


    로그인 인증토큰 발급

    로그인이 성공하면 AccessToken 을 발급해줍니다. 하지만 쿠키에 저장이 되어 있으며, 보안상 Javascript로 쿠키에 접근할 수 없도록 HttpOnly 옵션을 true로 설정합니다. 지금은 토큰값을 직접 넣어준 형태로 되어있지만, JWT 라이브러리를 적용해보겠습니다.

     

    app.post('/api/login', csrfProtection, function(req, res) {
      res.cookie('AccessToken', '***Auth token value***',  {
        httpOnly: true,
        expires: 0 // 브라우저를 닫을시 사라짐
      });
      res.json({});
    });

     

    우선은 환경변수에 토큰 시크릿 코드를 먼저 등록해 놓겠습니다. process.env로 시작하는 환경변수는 프로젝트 폴더에 .env 라는 파일을 생성하여 키=값 형태로 저장하여 사용하실 수 있습니다. node 모듈 중에서 require('crypto').randomBytes(64).toString('hex') 코드를 사용하면 코드를 생성할 수 있습니다. 지금은 직접 기입하도록 하겠습니다.

     

    TOKEN_SECRET=09f26e402586e2faa8da4c98a35f1b20d6b033c6097befa8be3486a829587fe2f90a832bd3ff9d42710a4da095a2ce285b009f0c3730cd9b8e1af3eb84df6611

     

    다음으로 jsonwebtoken 라이브러리를 사용하여 jwt 토큰을 생성하는 방법을 알아보겠습니다. 아래의 코드를 이용해여 웹 토큰 생성 함수를 만들 수 있습니다. 세번째 인자인 expiresIn 은 토큰의 만료 시간을 나타냅니다.

     

    // .env 파일 읽기
    const dotenv = require("dotenv");
    dotenv.config();
    
    // JWT 토큰 라이브러리 가져오기
    const jwt = require("jsonwebtoken");
    
    // JWT 토큰 생성 함수
    function generateAccessToken(username) {
      return jwt.sign(username, process.env.ACCESS_TOKEN_SECRET, {
        expiresIn: '1800s' // 30분마다 갱신해야함
      });
    }

     

    로그인 요청에서 이제 JWT 토큰 생성 함수를 적용해보면 다음과 같이 작성할 수 있습니다.

     

    app.post('/api/login', csrfProtection, function(req, res) {
      res.cookie('AccessToken', generateAccessToken({ username: req.body.username }),  {
        httpOnly: true,
        expires: 0 // 브라우저를 닫을시 사라짐
      });
      res.json({});
    });

     


    발급된 토큰 검증하기

    jwt 토큰을 검증하기 위한 함수를 아래와 같이 작성한 다음, 로그인 이후에 허용되는 요청에 대해서는 아래와같이 verifyToken 함수를 미들웨어로 추가해 주시면 됩니다.

     

    function verifyToken(req, res){
      // 쿠키에서 토큰 가져오기
      var token = req.cookies['AccessToken'];
      
      // 토큰 검증하기
      jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
        if (err) {
          return res.sendStatus(403);
        }
    
        req.user = user;
        next();
      })
    }
    
    app.get('/api/stats', verifyToken, function(req, res) {
      res.json({});
    });

     


    정리 - GET vs POST, PUT, DELETE

    결과적으로 GET 방식 요청에는 verifyToken 하나만 사용하면 되고, 나머지 요청방식에서는 csrfProtection, verifyToken 두가지의 미들웨어를 사용해주시면 될 것 같습니다.

     

    app.get('/api/get', verifyToken, function(req, res) { res.json({}) });
    
    app.post('/api/post', csrfProtection, verifyToken, function(req, res) { res.json({}) });
    app.put('/api/put', csrfProtection, verifyToken, function(req, res) { res.json({}) });
    app.delete('/api/delete', csrfProtection, verifyToken, function(req, res) { res.json({}) });