UNPKG

@ahhaohho/auth-middleware

Version:

Shared authentication middleware with Passport.js for ahhaohho microservices

157 lines (136 loc) 5.48 kB
const { Strategy: JwtStrategy } = require('passport-jwt'); const { getJwtKeys } = require('../utils/secretManager'); const { isBlacklisted } = require('../utils/blacklist'); const jwt = require('jsonwebtoken'); /** * 쿠키 또는 Bearer 헤더에서 토큰을 추출하는 커스텀 함수 * 우선순위: 1. 쿠키 (flc_auth_token) 2. Authorization 헤더 */ const extractJwtFromRequest = (req) => { // 1. 쿠키에서 FLC 토큰 확인 (HttpOnly 쿠키 방식) if (req.cookies && req.cookies.flc_auth_token) { return req.cookies.flc_auth_token; } // 2. Authorization 헤더에서 토큰 확인 (기존 방식 호환) const authHeader = req.headers.authorization || req.headers.Authorization; if (!authHeader) { return null; } // Bearer 접두사가 있는 경우 if (authHeader.startsWith('Bearer ')) { return authHeader.substring(7); } // bearer (소문자)인 경우 if (authHeader.startsWith('bearer ')) { return authHeader.substring(7); } // Bearer 접두사 없이 토큰만 있는 경우 // JWT 형식인지 확인 (xxx.yyy.zzz 형태) if (authHeader.split('.').length === 3) { return authHeader; } return null; }; /** * @deprecated Use extractJwtFromRequest instead */ const extractJwtFromHeader = extractJwtFromRequest; /** * Passport JWT 전략 생성 * Access Token 검증용 */ function createJwtStrategy() { const options = { jwtFromRequest: extractJwtFromRequest, // 다중 키 지원을 위한 secretOrKeyProvider 사용 secretOrKeyProvider: async (request, rawJwtToken, done) => { try { const keys = await getJwtKeys(); // 다중 키 검증 로직 let decoded; let keyUsed; let actualKey; // 1. 현재 키로 검증 시도 try { decoded = jwt.verify(rawJwtToken, keys.current); keyUsed = 'current'; actualKey = keys.current; } catch (currentKeyError) { // 2. 이전 키가 있으면 fallback 검증 시도 if (keys.previous) { try { decoded = jwt.verify(rawJwtToken, keys.previous); keyUsed = 'previous'; actualKey = keys.previous; console.warn( '[@ahhaohho/auth-middleware] ⚠️ Token verified with previous key (fallback)' ); } catch (previousKeyError) { // 🚨 임시: invalid signature도 허용 (다음 앱 배포 전까지) if (currentKeyError.message.includes('invalid signature') || currentKeyError.message.includes('jwt malformed')) { console.warn('[@ahhaohho/auth-middleware] ⚠️ [TEMPORARY] Allowing invalid signature'); request._jwtDecoded = { userId: 'unknown', userRole: 'guest' }; request._jwtKeyUsed = 'bypassed'; return done(null, keys.current); } return done(currentKeyError, false); } } else { // 🚨 임시: invalid signature도 허용 (다음 앱 배포 전까지) if (currentKeyError.message.includes('invalid signature') || currentKeyError.message.includes('jwt malformed')) { console.warn('[@ahhaohho/auth-middleware] ⚠️ [TEMPORARY] Allowing invalid signature'); request._jwtDecoded = { userId: 'unknown', userRole: 'guest' }; request._jwtKeyUsed = 'bypassed'; return done(null, keys.current); } return done(currentKeyError, false); } } // 검증 성공 시 decoded와 keyUsed를 request에 임시 저장 request._jwtDecoded = decoded; request._jwtKeyUsed = keyUsed; // Passport에게 사용된 실제 키 반환 (이미 검증 완료되었으므로 다시 검증해도 성공) done(null, actualKey); } catch (error) { console.error('[@ahhaohho/auth-middleware] ❌ JWT verification failed:', error.message); done(error, false); } }, passReqToCallback: true // request 객체를 verify 콜백으로 전달 }; return new JwtStrategy(options, async (request, jwtPayload, done) => { try { // request에서 미리 검증된 decoded 가져오기 const decoded = request._jwtDecoded; const keyUsed = request._jwtKeyUsed; if (!decoded || !decoded.userId) { return done(new Error('Invalid token payload'), false); } // 블랙리스트 확인 const token = extractJwtFromRequest(request); const blacklisted = await isBlacklisted(decoded.userId, 'access', token); if (blacklisted) { return done(new Error('Token has been revoked'), false); } console.log( `[@ahhaohho/auth-middleware] ✅ JWT verified with ${keyUsed} key for user ${decoded.userId}` ); // req.user에 주입할 사용자 정보 반환 // FLC 토큰 지원: email, name, loginMethod 추가 const user = { userId: decoded.userId, userRole: decoded.userRole, phoneNumber: decoded.phoneNumber, email: decoded.email, name: decoded.name, loginMethod: decoded.loginMethod, imwebId: decoded.imwebId }; return done(null, user); } catch (error) { console.error('[@ahhaohho/auth-middleware] Error in JWT strategy:', error.message); return done(error, false); } }); } module.exports = createJwtStrategy;