UNPKG

@ahhaohho/auth-middleware

Version:

Shared authentication middleware with Passport.js for ahhaohho microservices

218 lines (188 loc) 6.37 kB
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 };