@ahhaohho/auth-middleware
Version:
Shared authentication middleware with Passport.js for ahhaohho microservices
218 lines (188 loc) • 6.37 kB
JavaScript
const passport = require('passport');
const createJwtStrategy = require('../strategies/jwt.strategy');
const createRefreshStrategy = require('../strategies/refresh.strategy');
/**
* Passport 초기화 상태
*/
let initialized = false;
/**
* Passport 전략 초기화
* 한 번만 실행됨
*/
function initializePassport() {
if (!initialized) {
console.log('[@ahhaohho/auth-middleware] Initializing Passport strategies...');
passport.use('jwt', createJwtStrategy());
passport.use('refresh', createRefreshStrategy());
initialized = true;
console.log('[@ahhaohho/auth-middleware] Passport strategies initialized');
}
}
/**
* JWT Access Token 인증 미들웨어
*
* @example
* router.get('/verify', authenticateJWT, (req, res) => {
* res.json({ userId: req.user.userId });
* });
*/
function authenticateJWT(req, res, next) {
initializePassport();
passport.authenticate('jwt', { session: false }, (err, user, info) => {
if (err) {
console.error('[@ahhaohho/auth-middleware] Authentication error:', err.message);
return res.status(500).json({
error: 'Authentication error',
message: err.message
});
}
if (!user) {
return res.status(401).json({
error: 'Unauthorized',
message: info?.message || 'Invalid or expired token'
});
}
// req.user에 사용자 정보 주입
req.user = user;
next();
})(req, res, next);
}
/**
* Refresh Token 인증 미들웨어
*
* @example
* router.get('/refresh', authenticateRefresh, (req, res) => {
* // Generate new access token
* res.json({ newAccessToken: '...' });
* });
*/
function authenticateRefresh(req, res, next) {
initializePassport();
passport.authenticate('refresh', { session: false }, (err, user, info) => {
if (err) {
console.error('[@ahhaohho/auth-middleware] Refresh token error:', err.message);
return res.status(500).json({
error: 'Token refresh error',
message: err.message
});
}
if (!user) {
return res.status(401).json({
error: 'Invalid refresh token',
message: info?.message || 'Invalid or expired refresh token'
});
}
req.user = user;
next();
})(req, res, next);
}
/**
* 선택적 인증 미들웨어 (인증 실패해도 통과)
* req.user가 있으면 인증된 사용자, 없으면 비인증 사용자
*
* @example
* router.get('/public', optionalAuth, (req, res) => {
* if (req.user) {
* res.json({ message: 'Authenticated', userId: req.user.userId });
* } else {
* res.json({ message: 'Anonymous' });
* }
* });
*/
function optionalAuth(req, res, next) {
initializePassport();
passport.authenticate('jwt', { session: false }, (err, user) => {
// 에러나 인증 실패해도 통과
if (user) {
req.user = user;
}
next();
})(req, res, next);
}
/**
* Hybrid 인증 미들웨어 (access token 만료 시 refresh token으로 자동 갱신)
*
* 1. Access token 검증 시도
* 2. 실패하면 refresh token 확인
* 3. Refresh token이 유효하면 새 access token 생성하여 응답 헤더에 추가
*
* @example
* router.get('/protected', authenticateHybrid, (req, res) => {
* // req.user 사용 가능
* // 응답 헤더에 x-new-access-token이 있으면 클라이언트가 토큰 갱신해야 함
* res.json({ userId: req.user.userId });
* });
*/
async function authenticateHybrid(req, res, next) {
initializePassport();
// 1. Access token 검증 시도
passport.authenticate('jwt', { session: false }, async (err, user, info) => {
if (err) {
console.error('[@ahhaohho/auth-middleware] Hybrid auth error:', err.message);
return res.status(500).json({
error: 'Authentication error',
message: err.message
});
}
// Access token이 유효한 경우
if (user) {
req.user = user;
return next();
}
// 2. Access token이 유효하지 않은 경우, refresh token 확인
console.log('[@ahhaohho/auth-middleware] Access token invalid, trying refresh token...');
// Refresh token이 없으면 401 반환
if (!req.headers['refresh-token'] && !req.headers['refreshtoken']) {
return res.status(401).json({
error: 'Unauthorized',
message: 'Access token expired and no refresh token provided'
});
}
// 3. Refresh token 검증
passport.authenticate('refresh', { session: false }, async (refreshErr, refreshUser, refreshInfo) => {
console.log('[@ahhaohho/auth-middleware] 🔍 Refresh callback:', { hasError: !!refreshErr, hasUser: !!refreshUser, userId: refreshUser?.userId });
if (refreshErr) {
console.error('[@ahhaohho/auth-middleware] Refresh token error:', refreshErr.message);
return res.status(500).json({
error: 'Token refresh error',
message: refreshErr.message
});
}
if (!refreshUser) {
console.error('[@ahhaohho/auth-middleware] ❌ No refresh user found, returning 401');
return res.status(401).json({
error: 'Unauthorized',
message: 'Both access and refresh tokens are invalid'
});
}
// 4. 새 access token 생성
try {
const { signToken } = require('../utils/jwtValidator');
const tokenData = {
userId: refreshUser.userId,
userRole: refreshUser.userRole,
phoneNumber: refreshUser.phoneNumber
};
const newAccessToken = await signToken(tokenData, { expiresIn: '1h' });
console.log('[@ahhaohho/auth-middleware] ✅ New access token generated for userId:', refreshUser.userId);
// 5. 응답 헤더에 새 토큰 추가 (클라이언트가 이를 확인하여 저장)
res.setHeader('X-New-Access-Token', newAccessToken);
// 6. req.user 설정하고 계속 진행
req.user = refreshUser;
next();
} catch (tokenError) {
console.error('[@ahhaohho/auth-middleware] Failed to generate new token:', tokenError.message);
return res.status(500).json({
error: 'Token generation error',
message: tokenError.message
});
}
})(req, res, next);
})(req, res, next);
}
module.exports = {
authenticateJWT,
authenticateRefresh,
optionalAuth,
authenticateHybrid
};