2025년 1월 8일

김동환·2025년 1월 8일

오늘의 코드

require-access-token.middleware.js

import jwt from 'jsonwebtoken';
import { HTTP_STATUS } from '../constants/http-status.constant.js';
import { MESSAGES } from '../constants/message.constant.js';
import { ACCESS_TOKEN_SECRET } from '../constants/env.constant.js';
import { prisma } from '../utils/prisma.util.js';

export const requireAccessToken = async (req, res, next) => {
  try {
    // 인증 정보 파싱
    const authorization = req.headers.authorization;

    // Authorization이 없는 경우
    if (!authorization) {
      return res.status(HTTP_STATUS.UNAUTHORIZED).json({
        status: HTTP_STATUS.UNAUTHORIZED,
        message: MESSAGES.AUTH.COMMON.JWT.NO_TOKEN,
      });
    }

    // JWT 표준 인증 형태와 일치하지 않는 경우
    const [type, accessToken] = authorization.split(' ');

    if (type !== 'Bearer') {
      return res.status(HTTP_STATUS.UNAUTHORIZED).json({
        status: HTTP_STATUS.UNAUTHORIZED,
        message: MESSAGES.AUTH.COMMON.JWT.NOT_SUPPORTED_TYPE,
      });
    }

    // AccessToken이 없는 경우
    if (!accessToken) {
      return res.status(HTTP_STATUS.UNAUTHORIZED).json({
        status: HTTP_STATUS.UNAUTHORIZED,
        message: MESSAGES.AUTH.COMMON.JWT.NO_TOKEN,
      });
    }

    let payload;
    try {
      payload = jwt.verify(accessToken, ACCESS_TOKEN_SECRET);
    } catch (error) {
      // AccessToken의 유효기한이 지난 경우
      if (error.name === 'TokenExpiredError') {
        return res.status(HTTP_STATUS.UNAUTHORIZED).json({
          status: HTTP_STATUS.UNAUTHORIZED,
          message: MESSAGES.AUTH.COMMON.JWT.EXPIRED,
        });
      }
      // 그 밖의 AccessToken 검증에 실패한 경우
      else {
        return res.status(HTTP_STATUS.UNAUTHORIZED).json({
          status: HTTP_STATUS.UNAUTHORIZED,
          message: MESSAGES.AUTH.COMMON.JWT.INVALID,
        });
      }
    }

    // Payload에 담긴 사용자 ID와 일치하는 사용자가 없는 경우
    const { id } = payload;
    const user = await prisma.user.findUnique({
      where: { id },
      omit: { password: true },
    });

    if (!user) {
      return res.status(HTTP_STATUS.UNAUTHORIZED).json({
        status: HTTP_STATUS.UNAUTHORIZED,
        message: MESSAGES.AUTH.COMMON.JWT.NO_USER,
      });
    }

    req.user = user;
    next();
  } catch (error) {
    next(error);
  }
};

오늘 배운 것 (TIL): Node.js에서 JWT Access Token 인증 구현

주요 학습 내용:

  1. requireAccessToken 미들웨어의 역할:

    • 이 미들웨어는 HTTP 요청의 Authorization 헤더에 포함된 JSON Web Token(JWT)을 검증하여 사용자를 인증합니다. 인증에 성공하면 사용자 정보를 req 객체에 추가하여 이후의 라우트 핸들러에서 접근할 수 있도록 합니다.
  2. 코드 단계별 설명:

    • Authorization 헤더 파싱:
      • Authorization 헤더를 추출하고, 토큰이 존재하는지 확인합니다. 토큰이 없으면 401 Unauthorized 상태 코드와 함께 에러 응답을 반환합니다.
    • 토큰 구조 검증:
      • 토큰은 "Bearer " 형식을 따라야 하며, 형식이 잘못되었을 경우 401 Unauthorized 응답을 반환합니다.
    • 토큰 검증:
      • jsonwebtoken 라이브러리를 사용해 토큰의 서명을 ACCESS_TOKEN_SECRET으로 검증합니다. 만료되었거나 유효하지 않은 토큰은 각각 적절한 에러 메시지와 함께 처리됩니다.
    • 사용자 검증:
      • 검증된 토큰에서 사용자 ID를 추출하고, 이를 바탕으로 Prisma를 통해 데이터베이스에서 사용자를 조회합니다. 사용자가 존재하지 않을 경우 401 Unauthorized 응답을 반환합니다.
    • 요청 객체에 사용자 추가:
      • 모든 검증을 통과하면 사용자 데이터를 req.user에 추가하여 이후 미들웨어나 라우트 핸들러에서 사용할 수 있도록 합니다.
  3. 처리된 주요 에러 시나리오:

    • Authorization 헤더가 없거나 형식이 잘못된 경우.
    • 토큰 만료 (TokenExpiredError).
    • 토큰 서명이나 구조가 유효하지 않은 경우.
    • 토큰에 포함된 사용자 ID에 해당하는 사용자가 존재하지 않는 경우.
  4. 에러 처리:

    • 각 상황에 맞는 HTTP 상태 코드와 메시지를 반환하여 API 클라이언트가 문제를 명확히 파악할 수 있도록 합니다.
  5. 보안 고려 사항:

    • Access Token Secret: ACCESS_TOKEN_SECRET은 환경 변수나 안전한 설정 관리 도구를 사용해 안전하게 저장해야 하며, 코드에 하드코딩하지 않아야 합니다.
    • 민감한 데이터 제외: 데이터베이스에서 사용자 정보를 조회할 때 password 필드를 제외하여 민감한 데이터 노출을 최소화합니다.
  6. Prisma와의 통합:

    • Prisma의 prisma.user.findUnique 메서드를 사용해 사용자 ID로 데이터를 조회하며, 데이터베이스와의 긴밀한 통합을 통해 사용자 검증을 수행합니다.

주요 배움:

  • JWT는 상태를 유지하지 않는(stateless) 인증 메커니즘으로, 현대적인 API에 적합합니다.
  • 강력한 에러 처리와 명확한 메시지는 안전하고 사용자 친화적인 API 설계의 핵심입니다.
  • 이와 같은 미들웨어는 인증이 필요한 여러 경로에서 재사용할 수 있어 일관성을 제공합니다.

개선 가능성:

  • 커스텀 에러 클래스: 반복적인 응답 로직을 줄이고 에러 처리를 간소화하기 위해 커스텀 에러 클래스를 도입할 수 있습니다.
  • 로깅: 디버깅 및 감사(auditing)를 위해 인증 실패나 에러 발생 시 로깅 기능을 추가할 수 있습니다.
  • 요청 속도 제한: 무차별 대입 공격을 방지하기 위해 요청 속도를 제한하는 미들웨어와 결합할 수 있습니다.
  • 리프레시 토큰 지원: 만료된 토큰을 처리하기 위해 리프레시 토큰 메커니즘을 추가하는 것도 고려할 수 있습니다.

이 미들웨어는 API 보안을 위한 기초 레이어로, 인증과 에러 처리에 있어 모범 사례를 보여줍니다.

profile
Node.js 7기

0개의 댓글