정화 코딩

[Node.js] jwt 토큰으로 회원가입/로그인/로그아웃 구현 본문

Web Development

[Node.js] jwt 토큰으로 회원가입/로그인/로그아웃 구현

jungh150c 2024. 7. 22. 15:40

관련 패키지 (jsonwebtoken, cookie-parser) 설치

npm install jsonwebtoken
npm install cookie-parser --save

jwt.js

import jwt from 'jsonwebtoken';
const secretKey = process.env.JWT_SECRET_KEY;

// 새로운 토큰을 생성하는 함수
export const generateToken = (payload) => {
  const token = jwt.sign(payload, secretKey, { expiresIn: '10m' });
    return token;
};

// 기존 토큰을 사용하여 새로운 토큰을 생성하는 함수
export const refreshToken = (token) => {
  try {
    const decoded = jwt.verify(token, secretKey);
    const payload = {
      userId: decoded.userId,
      isAdmin: decoded.isAdmin,
    };
    const newToken = generateToken(payload);
    return newToken;
  } catch (error) {
    console.error('Error refreshing token:', error);
    return null;
  }
};

// 로그인이 필요한 api 요청 시
export function loginRequired(req, res, next) {
  // 헤더에서 토큰 가져오기
  const headerToken = req.headers['authorization']?.split(' ')[1];
  // 쿠키에서 토큰 가져오기
  const cookieToken = req.cookies.token;
  
  const token = headerToken || cookieToken;
  // 토큰이 없을 경우
  if (!token || token === "null") {
      console.log("Authorization 토큰: 없음");
      res.status(401).json({
          result: "forbidden-approach",
          message: "로그인한 유저만 사용할 수 있는 서비스입니다.",
      });
      return;
  }
  // 해당 토큰이 정상적인 토큰인지 확인
  try {
      const jwtDecoded = jwt.verify(token, secretKey);
      const userId = jwtDecoded.userId;
      req.currentUserId = userId;
      // 토큰 갱신
      const newToken = refreshToken(token);
      if (newToken) {
        res.cookie('token', newToken, { httpOnly: true, maxAge: 3600000 });
      }
      next();
  } catch (error) {
      res.status(401).json({
          result: "forbidden-approach",
          message: "정상적인 토큰이 아닙니다.",
      });
      return;
  }
}

app.js

import * as dotenv from 'dotenv';
dotenv.config();
import express from 'express';
import bcrypt from "bcryptjs";
import cookieParser from "cookie-parser";
import { PrismaClient, Prisma } from '@prisma/client';
import jwt from 'jsonwebtoken';
import { generateToken, loginRequired } from './jwt.js';
//import jwtt from './jwt.js';

const prisma = new PrismaClient();
const secretKey = process.env.JWT_SECRET_KEY;

const app = express();
app.use(express.json());
app.use(cookieParser());

// 회원가입
app.post("/register", asyncHandler(async (req, res) => {
  const { id, password } = req.body;
  const user = await prisma.user.findUnique({
    where: { id },
  });
  if (user) {
    return res.status(400).json({ message: "이미 존재하는 아이디입니다." });
  }
  const hashedPassword = bcrypt.hashSync(password, 10);
  const newUser = await prisma.user.create({
    data: {
      id: id,
      password: hashedPassword,
    },
  });
  res.status(201).send(newUser);
}));

// 로그인
app.post("/login", asyncHandler(async (req, res) => {
  const { id, password } = req.body;
  const user = await prisma.user.findUnique({
    where: { id },
  });
  // 해당 유저가 없는 경우
  if (!user) {
    console.log("가입되지 않은 아이디입니다.");
    return res.status(400).json({ message: "가입되지 않은 아이디입니다." });
  }
  // 비밀번호가 일치하는지 확인
  const isMatch = bcrypt.compareSync(password, user.password);
  if (isMatch) {
    const payload = { userId: id };
    const token = generateToken(payload);
    res.cookie('token', token, { httpOnly: true, maxAge: 3600000 });
    res.json({ message: "로그인 되었습니다." });
  } else {
    res.status(400).json({ message: "비밀번호가 일치하지 않습니다." });
  }
}));

// 로그아웃
app.post("/logout", asyncHandler(async (req, res) => {
  const token = req.cookies.token;
  // 토큰이 없을 경우
  if (!token) {
    res.status(400).json({ message: '토큰이 없습니다. 로그인 상태를 확안하세요.' });
    return;
  }
  // 토큰이 정상적인 토큰이 아닌 경우
  const decoded = jwt.decode(token);
  if (!decoded) {
      res.status(401).json({ message: '잘못된 토큰입니다. 로그인 상태를 확인하세요.' });
      return;
  }
  // 쿠키 삭제
  res.clearCookie('token');
  res.json({ message: '로그아웃 되었습니다.' });
}));

 

Comments