커스텀 csrf

유석현(SeokHyun Yu)·2022년 12월 12일
0

Node.js

목록 보기
24/29
post-thumbnail

csrf.js

import bcrypt from "bcrypt";

const csrf = async (req, res, next) => {
    if (req.method === "GET") {
        return next();
    }

    const csrfHeader = req.get("csrf-token");

    if (!csrfHeader) {
        return res.status(403).json({ message: "CSRF ERROR - 1" });
    }

    const isTrue = await bcrypt.compare("csrf", csrfHeader);

    if (!isTrue) {
        return res.status(403).json({ message: "CSRF ERROR - 2" });
    }

    next();
};

export default csrf;

app.js

import express from "express";
import helmet from "helmet";
import morgan from "morgan";
import cors from "cors";
import tweetController from "./tweet/tweet.controller.js";
import authController from "./auth/auth.controller.js";
import { config } from "../config.js";
import sequelize from "../database.js";
import csrf from "./middleware/csrf.js";

// 서버 생성
const app = express();

// 미들웨어
// csrf 토큰 검증 미들웨어 추가
app.use(csrf);
app.use(express.json());
app.use(morgan("dev"));
app.use(helmet());
app.use(cors());

// 라우터
app.use("/tweet", tweetController);
app.use("/auth", authController);

// 404 에러 핸들러
app.use((req, res, next) => {
    res.sendStatus(404);
});

// 500 에러 핸들러
app.use((err, req, res, next) => {
    res.sendStatus(500);
});

// DB 연결
sequelize.sync().then(() => {
    // 8000 포트로 listen
    // DB가 연결된 다음에 서버 실행
    app.listen(config.port, () => {
        console.log("Server On...");
    });
});

auth.controller.js

import express from "express";
import { body } from "express-validator";
import { auth } from "../middleware/auth.js";
import { validate } from "../middleware/validator.js";
import * as authService from "./auth.service.js";

const authController = express.Router();

// validation
const loginValidation = [
    body("username").trim().notEmpty().withMessage("username을 입력해주세요."),
    body("password")
        .trim()
        .isLength({ min: 5 })
        .withMessage("비밀번호는 최소 5글자 이상 입력해주세요."),
    validate,
];

const signUpValidation = [
    ...loginValidation,
    body("name").trim().notEmpty().withMessage("이름이 비어있습니다."),
    body("email")
        .isEmail()
        .normalizeEmail()
        .withMessage("이메일 형식에 맞지 않습니다"),
    validate,
];

// 회원가입
authController.post("/signup", signUpValidation, authService.signup);

// 로그인
authController.post("/login", loginValidation, authService.login);

// me
authController.get("/me", auth, authService.me);

// csrf
authController.get("/csrf", authService.csrfToken);

export default authController;

auth.service.js

import * as userRepository from "../user/user.repository.js";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { config } from "../../config.js";

const createToken = (id) => {
    return jwt.sign({ id }, config.jwtSecretKey, {
        expiresIn: config.jwtExpires,
    });
};

// 회원가입
export const signup = async (req, res) => {
    const { username, password, name, email } = req.body;

    // 이미 가입된 유저일 경우
    const user = await userRepository.findByUsername(username);

    if (user) {
        return res.status(409).json({ message: `이미 가입된 유저입니다.` });
    }

    // 비밀번호 암호화
    const hashed = await bcrypt.hash(password, config.bcryptSaltRounds);

    // 유저 데이터 생성
    const createdUserId = await userRepository.createUser({
        username,
        password: hashed,
        name,
        email,
    });

    // 토큰 생성
    const token = createToken(createdUserId);

    res.status(201).json({ token, username });
};

// 로그인
export const login = async (req, res) => {
    const { username, password } = req.body;

    // username을 가진 유저가 있는지 확인
    const user = await userRepository.findByUsername(username);

    if (!user) {
        return res
            .status(401)
            .json({ message: "아이디 혹은 비밀번호가 틀렸습니다." });
    }

    // bcrypt.compare()로 암호화 된 비밀번호와 비교
    const isValidPassword = await bcrypt.compare(password, user.password);

    if (!isValidPassword) {
        return res
            .status(401)
            .json({ message: "아이디 혹은 비밀번호가 틀렸습니다." });
    }

    // 토큰 생성
    const token = createToken(user.id);

    res.status(200).json({ token, username });
};

// me
export const me = async (req, res) => {
    const user = await userRepository.findById(req.userId);

    if (user) {
        return res
            .status(200)
            .json({ token: req.token, username: user.username });
    }

    res.status(404).json({ message: "유저가 존재하지 않습니다." });
};

// csrf
export const csrfToken = async (req, res) => {
    const csrfToken = await bcrypt.hash("csrf", 1);

    res.status(200).json({ csrfToken });
};

csrf용 토큰 발급 (GET: http://localhost:8000/auth/signup)

회원가입 (POST: http://localhost:8000/auth/signup)


csrf 토큰이 없을 때 (POST: http://localhost:8000/auth/signup)


csrf 토큰의 값이 유효하지 않을 때 (POST: http://localhost:8000/auth/signup)

profile
Backend Engineer

0개의 댓글