프로젝트 소개 및 목표
🔗 관련 에러 해결 가이드
JWT 인증 시스템 구현하기는 현대 웹 애플리케이션에서 가장 널리 사용되는 인증 방식을 직접 구축해보는 프로젝트입니다. JWT(JSON Web Token)는 stateless한 인증을 가능하게 하여 확장성이 뛰어나고, 마이크로서비스 아키텍처에 최적화되어 있습니다. 이 프로젝트를 통해 회원가입, 로그인, 토큰 발급, 토큰 검증, 리프레시 토큰 관리까지 실무에서 필요한 모든 인증 로직을 구현할 수 있습니다. 완성된 시스템은 포트폴리오로 활용하거나 실제 서비스의 기반으로 사용할 수 있으며, 보안 개념과 RESTful API 설계 원칙을 동시에 학습할 수 있는 최적의 실습 프로젝트입니다.
필요한 기술 스택
이 프로젝트는 Node.js와 Express를 백엔드 프레임워크로 사용하며, jsonwebtoken 라이브러리로 JWT를 생성하고 검증합니다. 데이터베이스는 MongoDB를 사용하고 Mongoose로 ODM을 구성합니다. 비밀번호 암호화를 위해 bcrypt를 사용하며, 환경 변수 관리는 dotenv로 처리합니다. 프론트엔드는 React와 Axios를 사용하여 API 통신을 구현하고, LocalStorage에 토큰을 저장합니다. 개발 환경에서는 Nodemon으로 자동 재시작을 설정하고, Postman으로 API 테스트를 진행합니다.
프로젝트 셋업
먼저 프로젝트 디렉토리를 생성하고 npm을 초기화합니다. mkdir jwt-auth-project && cd jwt-auth-project && npm init -y 명령으로 시작합니다. 필요한 패키지를 설치합니다: npm install express mongoose jsonwebtoken bcryptjs dotenv cors, 그리고 개발 의존성 npm install --save-dev nodemon을 추가합니다. 프로젝트 루트에 .env 파일을 생성하여 PORT=5000, MONGODB_URI=mongodb://localhost:27017/jwt-auth, JWT_SECRET=your-secret-key-here, JWT_REFRESH_SECRET=your-refresh-secret를 설정합니다. package.json의 scripts에 "dev": "nodemon server.js"를 추가하여 개발 서버를 실행할 준비를 마칩니다. 기본 폴더 구조는 models, controllers, routes, middleware로 구성합니다.
단계별 구현 과정
1단계: 서버 및 데이터베이스 설정
server.js 파일을 생성하고 Express 애플리케이션을 초기화합니다:
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
require('dotenv').config();
const app = express();
app.use(cors());
app.use(express.json());
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('MongoDB 연결 성공'))
.catch(err => console.error('MongoDB 연결 실패:', err));
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({
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true
},
password: {
type: String,
required: true,
minlength: 6
},
name: {
type: String,
required: true
},
refreshTokens: [{
token: String,
createdAt: {
type: Date,
default: Date.now,
expires: 604800 // 7일 후 자동 삭제
}
}]
}, { timestamps: true });
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
module.exports = mongoose.model('User', userSchema);
3단계: JWT 유틸리티 함수 작성
JWT 인증 시스템 구현하기의 핵심은 토큰 생성과 검증입니다. utils/jwt.js 파일을 생성합니다:
const jwt = require('jsonwebtoken');
const generateAccessToken = (userId) => {
return jwt.sign(
{ userId },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
};
const generateRefreshToken = (userId) => {
return jwt.sign(
{ userId },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '7d' }
);
};
const verifyAccessToken = (token) => {
try {
return jwt.verify(token, process.env.JWT_SECRET);
} catch (error) {
return null;
}
};
const verifyRefreshToken = (token) => {
try {
return jwt.verify(token, process.env.JWT_REFRESH_SECRET);
} catch (error) {
return null;
}
};
module.exports = {
generateAccessToken,
generateRefreshToken,
verifyAccessToken,
verifyRefreshToken
};
4단계: 인증 미들웨어 구현
middleware/auth.js 파일에서 토큰 검증 미들웨어를 작성합니다:
const { verifyAccessToken } = require('../utils/jwt');
const authMiddleware = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ message: '인증 토큰이 없습니다' });
}
const token = authHeader.split(' ')[1];
const decoded = verifyAccessToken(token);
if (!decoded) {
return res.status(401).json({ message: '유효하지 않은 토큰입니다' });
}
req.userId = decoded.userId;
next();
};
module.exports = authMiddleware;
5단계: 인증 컨트롤러 작성
controllers/authController.js에서 회원가입, 로그인, 토큰 갱신 로직을 구현합니다:
const User = require('../models/User');
const { generateAccessToken, generateRefreshToken, verifyRefreshToken } = require('../utils/jwt');
const register = async (req, res) => {
try {
const { email, password, name } = req.body;
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ message: '이미 존재하는 이메일입니다' });
}
const user = new User({ email, password, name });
await user.save();
const accessToken = generateAccessToken(user._id);
const refreshToken = generateRefreshToken(user._id);
user.refreshTokens.push({ token: refreshToken });
await user.save();
res.status(201).json({
message: '회원가입 성공',
accessToken,
refreshToken,
user: { id: user._id, email: user.email, name: user.name }
});
} catch (error) {
res.status(500).json({ message: '서버 오류', error: error.message });
}
};
const login = async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ message: '이메일 또는 비밀번호가 잘못되었습니다' });
}
const isPasswordValid = await user.comparePassword(password);
if (!isPasswordValid) {
return res.status(401).json({ message: '이메일 또는 비밀번호가 잘못되었습니다' });
}
const accessToken = generateAccessToken(user._id);
const refreshToken = generateRefreshToken(user._id);
user.refreshTokens.push({ token: refreshToken });
await user.save();
res.json({
message: '로그인 성공',
accessToken,
refreshToken,
user: { id: user._id, email: user.email, name: user.name }
});
} catch (error) {
res.status(500).json({ message: '서버 오류', error: error.message });
}
};
const refresh = async (req, res) => {
try {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({ message: '리프레시 토큰이 필요합니다' });
}
const decoded = verifyRefreshToken(refreshToken);
if (!decoded) {
return res.status(401).json({ message: '유효하지 않은 리프레시 토큰입니다' });
}
const user = await User.findById(decoded.userId);
if (!user || !user.refreshTokens.some(rt => rt.token === refreshToken)) {
return res.status(401).json({ message: '리프레시 토큰을 찾을 수 없습니다' });
}
const newAccessToken = generateAccessToken(user._id);
res.json({
accessToken: newAccessToken
});
} catch (error) {
res.status(500).json({ message: '서버 오류', error: error.message });
}
};
const logout = async (req, res) => {
try {
const { refreshToken } = req.body;
const user = await User.findById(req.userId);
if (user) {
user.refreshTokens = user.refreshTokens.filter(rt => rt.token !== refreshToken);
await user.save();
}
res.json({ message: '로그아웃 성공' });
} catch (error) {
res.status(500).json({ message: '서버 오류', error: error.message });
}
};
module.exports = { register, login, refresh, logout };
6단계: 라우트 설정
routes/auth.js 파일에서 API 엔드포인트를 정의합니다:
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
const authMiddleware = require('../middleware/auth');
router.post('/register', authController.register);
router.post('/login', authController.login);
router.post('/refresh', authController.refresh);
router.post('/logout', authMiddleware, authController.logout);
module.exports = router;
server.js에 라우트를 연결합니다:
const authRoutes = require('./routes/auth');
app.use('/api/auth', authRoutes);
// 보호된 라우트 예시
app.get('/api/protected', authMiddleware, (req, res) => {
res.json({ message: '보호된 리소스에 접근했습니다', userId: req.userId });
});
테스트 및 배포
JWT 인증 시스템 구현하기 프로젝트의 테스트는 Postman이나 Thunder Client를 사용합니다. 먼저 POST /api/auth/register로 회원가입을 테스트하고, 반환된 accessToken과 refreshToken을 확인합니다. POST /api/auth/login으로 로그인 기능을 검증하고, GET /api/protected 엔드포인트에 Authorization 헤더에 Bearer {accessToken}을 포함하여 보호된 리소스 접근을 테스트합니다. 토큰 만료 후 POST /api/auth/refresh로 토큰 갱신이 정상 작동하는지 확인합니다. 배포는 Heroku나 AWS EC2를 사용하며, MongoDB Atlas로 프로덕션 데이터베이스를 설정합니다. 환경 변수는 반드시 .env 파일로 관리하고 .gitignore에 추가하여 보안을 유지합니다. HTTPS를 필수로 적용하여 토큰 전송 시 암호화를 보장합니다.
마무리 및 확장 아이디어
이제 완성된 JWT 인증 시스템 구현하기 프로젝트를 기반으로 다양한 기능을 추가할 수 있습니다. 이메일 인증 시스템을 구축하여 회원가입 시 인증 메일을 발송하거나, 비밀번호 재설정 기능을 추가할 수 있습니다. OAuth2.0을 통합하여 구글, 페이스북 소셜 로그인을 지원하거나, 역할 기반 접근 제어(RBAC)를 구현하여 관리자와 일반 사용자를 구분할 수 있습니다. Redis를 활용한 토큰 블랙리스트 관리, Rate Limiting으로 무차별 대입 공격 방어, 2단계 인증(2FA) 추가 등으로 보안을 강화할 수 있습니다. 이 프로젝트는 포트폴리오의 핵심 프로젝트로 활용하기에 최적입니다!
📚 함께 읽으면 좋은 글
MongoDB와 Express.js로 블로그 만들기 – 완성까지 한번에!
📅 2025. 11. 23.
🎯 MongoDB와 Express.js로 블로그 만들기
React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!
📅 2025. 11. 22.
🎯 React + Node.js 풀스택 앱 배포하기
실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!
📅 2025. 11. 21.
🎯 실시간 채팅 앱 만들기 with Socket.io
MongoDB와 Express.js로 블로그 만들기 – 완성까지 한번에!
📅 2025. 11. 20.
🎯 MongoDB와 Express.js로 블로그 만들기
React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!
📅 2025. 11. 20.
🎯 React + Node.js 풀스택 앱 배포하기
💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!
📢 이 글이 도움되셨나요? 공유해주세요!
여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨
🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏
💬 여러분의 소중한 의견을 들려주세요!
여러분은 JWT 인증 시스템 구현하기에 대해 어떻게 생각하시나요?
⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨
🔔 블로그 구독하고 최신 글을 받아보세요!
🌟 프로젝트 아이디어부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨
📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!