🛠️ JWT 인증 시스템 구현하기 – 완성까지 한번에!

개발 에러 해결 가이드 - FixLog 노트

프로젝트 소개 및 목표

JWT 인증 시스템 구현하기는 현대 웹 애플리케이션에서 가장 널리 사용되는 인증 방식을 직접 구축해보는 프로젝트입니다. JSON Web Token(JWT)을 활용한 stateless 인증 시스템을 처음부터 끝까지 만들어보면서, 사용자 회원가입, 로그인, 토큰 발급 및 검증, 보호된 라우트 접근 등 실무에서 반드시 필요한 인증 로직을 완벽하게 이해할 수 있습니다. 이 프로젝트를 통해 보안의 기본 원칙을 배우고, RESTful API 설계 능력을 향상시키며, 포트폴리오에 추가할 수 있는 완성도 높은 결과물을 얻게 됩니다. 백엔드 개발자를 목표로 하는 분들에게 특히 유용한 실습 프로젝트입니다.

필요한 기술 스택

이 프로젝트를 성공적으로 완성하기 위해서는 다음 기술 스택이 필요합니다. 백엔드: Node.js, Express.js 프레임워크, jsonwebtoken 라이브러리, bcrypt(비밀번호 해싱). 데이터베이스: MongoDB와 Mongoose ORM 또는 PostgreSQL과 Sequelize ORM. 프론트엔드: React.js, Axios(HTTP 클라이언트), localStorage(토큰 저장). 개발 도구: Postman(API 테스트), nodemon(개발 서버 자동 재시작). 모든 기술은 초보자도 충분히 학습 가능한 수준이며, 단계별로 차근차근 따라하면 됩니다.

프로젝트 셋업

먼저 프로젝트 디렉토리를 생성하고 필요한 패키지를 설치합니다. 터미널에서 다음 명령어를 실행하세요:

mkdir jwt-auth-system
cd jwt-auth-system
npm init -y
npm install express mongoose jsonwebtoken bcryptjs dotenv cors
npm install --save-dev nodemon

package.json 파일의 scripts 섹션에 개발 서버 실행 스크립트를 추가합니다:

"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js"
}

프로젝트 루트에 .env 파일을 생성하여 환경 변수를 설정합니다:

PORT=5000
MONGO_URI=mongodb://localhost:27017/jwtauth
JWT_SECRET=your_super_secret_key_change_this_in_production
JWT_EXPIRE=7d

기본 폴더 구조를 다음과 같이 구성합니다: models(데이터 모델), routes(API 라우트), middleware(인증 미들웨어), controllers(비즈니스 로직).

단계별 구현 과정

1단계: 서버 및 데이터베이스 설정

server.js 파일을 생성하고 Express 서버와 MongoDB 연결을 설정합니다:

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
require('dotenv').config();

const app = express();

// 미들웨어
app.use(express.json());
app.use(cors());

// MongoDB 연결
mongoose.connect(process.env.MONGO_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true
})
.then(() => console.log('MongoDB 연결 성공'))
.catch(err => console.error('MongoDB 연결 실패:', err));

// 라우트
app.use('/api/auth', require('./routes/auth'));
app.use('/api/users', require('./routes/users'));

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`서버가 포트 ${PORT}에서 실행 중입니다`));

2단계: 사용자 모델 생성

models/User.js 파일을 생성하여 사용자 스키마를 정의합니다:

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const UserSchema = new mongoose.Schema({
  username: {
    type: String,
    required: [true, '사용자명을 입력해주세요'],
    unique: true,
    trim: true,
    minlength: 3
  },
  email: {
    type: String,
    required: [true, '이메일을 입력해주세요'],
    unique: true,
    match: [/^\S+@\S+\.\S+$/, '유효한 이메일 주소를 입력해주세요']
  },
  password: {
    type: String,
    required: [true, '비밀번호를 입력해주세요'],
    minlength: 6,
    select: false
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

// 비밀번호 해싱 미들웨어
UserSchema.pre('save', async function(next) {
  if (!this.isModified('password')) {
    next();
  }
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
});

// 비밀번호 검증 메서드
UserSchema.methods.matchPassword = async function(enteredPassword) {
  return await bcrypt.compare(enteredPassword, this.password);
};

module.exports = mongoose.model('User', UserSchema);

3단계: JWT 토큰 생성 유틸리티

utils/generateToken.js 파일을 생성합니다:

const jwt = require('jsonwebtoken');

const generateToken = (userId) => {
  return jwt.sign({ id: userId }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRE
  });
};

module.exports = generateToken;

4단계: 인증 컨트롤러 구현

controllers/authController.js 파일에서 회원가입과 로그인 로직을 구현합니다:

const User = require('../models/User');
const generateToken = require('../utils/generateToken');

// 회원가입
exports.register = async (req, res) => {
  try {
    const { username, email, password } = req.body;

    // 사용자 존재 확인
    const userExists = await User.findOne({ $or: [{ email }, { username }] });
    if (userExists) {
      return res.status(400).json({ message: '이미 존재하는 사용자입니다' });
    }

    // 사용자 생성
    const user = await User.create({ username, email, password });

    // 토큰 생성 및 응답
    const token = generateToken(user._id);
    res.status(201).json({
      success: true,
      token,
      user: {
        id: user._id,
        username: user.username,
        email: user.email
      }
    });
  } catch (error) {
    res.status(500).json({ message: '서버 오류', error: error.message });
  }
};

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

    // 입력 검증
    if (!email || !password) {
      return res.status(400).json({ message: '이메일과 비밀번호를 모두 입력해주세요' });
    }

    // 사용자 조회 (비밀번호 포함)
    const user = await User.findOne({ email }).select('+password');
    if (!user) {
      return res.status(401).json({ message: '잘못된 인증 정보입니다' });
    }

    // 비밀번호 검증
    const isPasswordValid = await user.matchPassword(password);
    if (!isPasswordValid) {
      return res.status(401).json({ message: '잘못된 인증 정보입니다' });
    }

    // 토큰 생성 및 응답
    const token = generateToken(user._id);
    res.json({
      success: true,
      token,
      user: {
        id: user._id,
        username: user.username,
        email: user.email
      }
    });
  } catch (error) {
    res.status(500).json({ message: '서버 오류', error: error.message });
  }
};

5단계: 인증 미들웨어 구현

middleware/auth.js 파일을 생성하여 보호된 라우트를 위한 미들웨어를 작성합니다:

const jwt = require('jsonwebtoken');
const User = require('../models/User');

const protect = async (req, res, next) => {
  let token;

  // Authorization 헤더에서 토큰 추출
  if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
    token = req.headers.authorization.split(' ')[1];
  }

  // 토큰 존재 확인
  if (!token) {
    return res.status(401).json({ message: '인증 토큰이 없습니다' });
  }

  try {
    // 토큰 검증
    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    // 사용자 정보 조회 및 req 객체에 추가
    req.user = await User.findById(decoded.id).select('-password');
    
    if (!req.user) {
      return res.status(401).json({ message: '사용자를 찾을 수 없습니다' });
    }

    next();
  } catch (error) {
    return res.status(401).json({ message: '유효하지 않은 토큰입니다' });
  }
};

module.exports = protect;

6단계: 라우트 설정

routes/auth.js 파일을 생성합니다:

const express = require('express');
const router = express.Router();
const { register, login } = require('../controllers/authController');

router.post('/register', register);
router.post('/login', login);

module.exports = router;

routes/users.js 파일을 생성하여 보호된 라우트 예시를 만듭니다:

const express = require('express');
const router = express.Router();
const protect = require('../middleware/auth');
const User = require('../models/User');

// 프로필 조회 (보호된 라우트)
router.get('/profile', protect, async (req, res) => {
  try {
    res.json({
      success: true,
      user: req.user
    });
  } catch (error) {
    res.status(500).json({ message: '서버 오류', error: error.message });
  }
});

// 모든 사용자 조회 (보호된 라우트)
router.get('/', protect, async (req, res) => {
  try {
    const users = await User.find().select('-password');
    res.json({
      success: true,
      count: users.length,
      users
    });
  } catch (error) {
    res.status(500).json({ message: '서버 오류', error: error.message });
  }
});

module.exports = router;

테스트 및 배포

개발 서버를 실행하고 Postman으로 API를 테스트합니다:

npm run dev

회원가입 테스트: POST http://localhost:5000/api/auth/register (Body: JSON으로 username, email, password 전송). 로그인 테스트: POST http://localhost:5000/api/auth/login (Body: JSON으로 email, password 전송). 보호된 라우트 테스트: GET http://localhost:5000/api/users/profile (Headers에 Authorization: Bearer {받은_토큰} 추가). 모든 엔드포인트가 정상 작동하면 배포 준비가 완료된 것입니다. Heroku, AWS, 또는 Vercel 등의 플랫폼을 활용해 배포할 수 있으며, 환경 변수는 반드시 배포 플랫폼의 설정에서 안전하게 관리해야 합니다. 프로덕션 환경에서는 HTTPS를 반드시 사용하고, JWT_SECRET은 강력한 랜덤 문자열로 설정하세요.

마무리 및 확장 아이디어

JWT 인증 시스템 구현하기 프로젝트를 완성했습니다! 이제 기본적인 인증 시스템을 이해했으니, 다음 단계로 확장해보세요. 리프레시 토큰 추가로 보안 강화, 이메일 인증 기능 구현, 소셜 로그인(Google, GitHub OAuth) 통합, 역할 기반 접근 제어(RBAC) 구현, 비밀번호 재설정 기능 추가 등이 있습니다. 이 프로젝트는 포트폴리오에 추가하기에 완벽하며, 실무에서도 바로 활용할 수 있는 코드 베이스가 됩니다. 계속해서 기능을 추가하고 개선하며 실력을 향상시켜보세요!

📚 함께 읽으면 좋은 글

1

실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 25.
🎯 실시간 채팅 앱 만들기 with Socket.io

2

JWT 인증 시스템 구현하기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 24.
🎯 JWT 인증 시스템 구현하기

3

JWT 인증 시스템 구현하기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 23.
🎯 JWT 인증 시스템 구현하기

4

MongoDB와 Express.js로 블로그 만들기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 22.
🎯 MongoDB와 Express.js로 블로그 만들기

5

30분만에 만드는 Todo App 완성 가이드 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 21.
🎯 30분만에 만드는 Todo App 완성 가이드

💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!

📢 이 글이 도움되셨나요? 공유해주세요!

여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨


📘 페이스북


🐦 트위터


✈️ 텔레그램

🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏

💬 여러분의 소중한 의견을 들려주세요!

이 글에서 가장 도움이 된 부분은 어떤 것인가요?

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨

🔔 블로그 구독하고 최신 글을 받아보세요!

📚
다양한 주제
17개 카테고리

정기 업데이트
하루 3회 발행

🎯
실용적 정보
바로 적용 가능

💡
최신 트렌드
2025년 기준

🌟 프로젝트 아이디어부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨

📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!

📱 전체 버전 보기