Authentication, Authorization

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

Node.js

목록 보기
20/29
post-thumbnail

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";

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

// 미들웨어
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);
});

// 8000 포트로 listen
app.listen(8000);

auth.js

import jwt from "jsonwebtoken";
import * as userRepository from "../user/user.repository.js";

export const auth = async (req, res, next) => {
    // Header의 Authorization 키 안에 담겨있는 토큰을 가져온다
    const authHeader = req.get("Authorization");

    // 토큰이 있고 "Bearer"로 시작하는 토큰이라면
    if (authHeader && authHeader.startsWith("Bearer ")) {
        // "Bearer"와 토큰 string을 분리
        const token = authHeader.split(" ")[1];

        // 토큰값 검증
        jwt.verify(token, "secret", async (err, data) => {
            // 토큰이 유효하지 않다면
            if (err) {
                res.status(401).json({ message: "인증 에러" });
            }
            // 토큰이 유효하다면
            else {
                // 토큰이 유효하더라도 토큰에 담겨있는 id값이 실제로 존재하는지 한 번 더 확인
                const user = await userRepository.findById(data.id);

                // 유저가 존재하면
                if (user) {
                    req.token = token;
                    req.userId = user.id;

                    next();
                }
                // 유저가 존재하지 않다면
                else {
                    res.status(401).json({ message: "인증 에러" });
                }
            }
        });
    }
    else {
        res.status(401).json({ message: "인증 에러" });
    }
};

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);

export default authController;

auth.service.js

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

const jwtSecretKey = "secret";
const jwtExpiresInDays = "2d";
const bcryptSaltRounds = 12;

// jwt 토큰 생성
const createToken = (id) => {
    return jwt.sign({ id }, jwtSecretKey, { expiresIn: jwtExpiresInDays });
};

// 회원가입
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, bcryptSaltRounds);

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

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

    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: "유저가 존재하지 않습니다." });
};

tweet.controller.js

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

const tweetController = express.Router();

const textValidator = [
    body("text")
        .trim()
        .isLength({ min: 5 })
        .withMessage("text를 최소 5 글자 이상 입력해주세요."),
    validate,
];

// 이제부터 tweet의 모든 api는 jwt 토큰을 가지고 있고 또 그 토큰이 유효해야 요청할 수 있다
// 전체 조회
tweetController.get("/", auth, tweetService.getTweets);

// 상세 조회
tweetController.get("/:id", auth, tweetService.getTweet);

// 트윗 생성
tweetController.post("/", auth, textValidator, tweetService.createTweet);

// 트윗 수정
tweetController.put("/:id", auth, textValidator, tweetService.updateTweet);

// 트윗 삭제
tweetController.delete("/:id", auth, tweetService.deleteTweet);

export default tweetController;

1. 토큰 없이 API 요청 (401: Unauthorized)


2. 토큰을 담아서 API 요청 (200)


3. 다른 유저의 토큰을 담아서 API 요청 (403: Forbidden)

profile
Backend Engineer

0개의 댓글