validate

import { validationResult } from 'express-validator';

export const validate = (req, res, next) => {
  const errors = validationResult(req); // req ★
  if (errors.isEmpty()) {
    return next();
  }
  res.status(400).json({ message: errors.array()[0].msg });
};

*isEmpty : 하나라도 들어 있다면, 즉 비어 있지 않다면 에러!*

auth

import jwt from 'jsonwebtoken';

import * as userRepository from '../data/auth.js';
import { config } from '../config.js'

const AUTH_ERROR = { message: 'Authorization' };

export const isAuth = async (req, res, next) => {
	let token;
	// 1. check the cookie (브라우저용)
	// Cookie (for Browser Client)
	token = req.cookies['token'];

	// 2. check the header (브라우저 외 클라이언트용)
  // Header (for Non-Browser Client)
	if(!token) {
		const authHeader = req.get('Authorization');
	  if ((authHeader && authHeader.startsWith('Bearer'))) {
			token = authHeader.split(' ')[1];
		  // Bearer $2sdl@#$@$SDFSAF... : 토큰 데이터를 가져오기 위해 [1] 배열 index
	  }
	}

	if(!token) {
    return res.status(401).json(AUTH_ERROR);
	}

  jwt.verify(
    token,
    config.jwt.secretKey,
    async (error, decoded) => { // decoded : secret 키로 token 을 인증하면 받는 값
      if (error) {
        return res.status(401).json(AUTH_ERROR);
      }
      const user = await userRepository.findById(decoded.id);
      if (!user) {
        return res.status(401).json(AUTH_ERROR);
      }
      req.userId = user.id; // req.customData (request에 사용자 지정 데이터를 추가할 수 있다.)
      req.token = token;
      next();
    }
  )
  
}

어떤 해당 서비스를 이용하고자 할 때 유저 정보가 존재하는지 확인하는 미들웨어로, 로그인이 된 사용자만 서비스를 이용하도록 하기 위해서 사용자 정보(쿠키에 저장된 토큰의 데이터 정보) 를 확인하는 과정을 거쳐서 확인된 사용자만 서비스를 요청에 대한 응답을 받을 수 있다. → 쿠키 & JWT 활용

csrf

import bcrypt from 'bcrypt';
import { config } from '../config.js';

export const csrfCheck = async (req, res, next) => {
  if (
    req.method === 'GET' ||
    req.method === 'HEAD' ||
    req.method === 'OPTIONS'
  ) {
    return next();
  }

  const csrfHeader = req.get('dwitter-csrf-token');
  
  if (!csrfHeader) {
    console.warn('Missing required "dwitter-csrf-token" header.', req.headers.origin);
    return res.status(403).json({ message: 'Faild CSRF check' });
  }

  try {
    const valid = await validateCsrfToken(csrfHeader);
    if (!valid) {
      console.warn(
        'Value provided in "dwitter-csrf-token" header does not validate',
        req.headers.origin,
        csrfHeader
      );
      return res.status(403).json({ message: 'Failed CSRF check' });
    }
    next();
  } catch (error) {
    console.log(error);
    return res.status(500).json({ message: 'Something went wrong' });
  }
  
};

async function validateCsrfToken(csrfHeader) {
  return bcrypt.compare(config.csrf.plainToken, csrfHeader);
};

정상적인 경로를 통한 액션만 동작하게끔 하기 위하여 브라우저와 서버간의 서로 대칭키를 가지고 있게하여 해당 키가 없다면 POST, PUT, DELETE 같은 액션을 할 수 없도록 한다.

→ CSRF Attack 에 대한 보안방법

rate-limiter

import rateLimit from 'express-rate-limit';

import { config } from '../config.js';

// 호출 시 middleware callback 함수 반환
export default rateLimit({
  windowMs: config.rateLimit.windowMs, // ms 단위만 가능하다.
  max: config.rateLimit.maxRequest, // 위 시간동안 처리가능한 요청 개수 (요청이란 GET, POST, PUT, DELETE 등의 요청을 말한다.)
  keyGenerator: (req, res) => 'dwitter-limit', // 식별을 위한 key 이름 지정
});