@ahhaohho/auth-middleware
Version:
Shared authentication middleware with Passport.js for ahhaohho microservices
157 lines (136 loc) • 5.48 kB
JavaScript
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;