Post

[Trouble Shooting] JWT roles 배열 불일치로 인한 빈 응답 문제

Portal에서 roles를 배열로 발급하는데 하위 서비스에서 단수 role로 읽어 빈 데이터가 반환되는 문제 해결

문제 상황

멀티 서비스 포탈 구조에서, 특정 하위 서비스의 데이터 조회 API가 항상 빈 배열을 반환하는 현상이 발생함.

  • 로그인은 정상적으로 됨
  • 다른 서비스는 정상 동작
  • 해당 서비스만 데이터가 안 나옴

추가로, 로그인 실패 시 “비밀번호가 틀렸습니다” 대신 “인증이 만료되었습니다” 라는 부정확한 에러 메시지가 표시되는 문제도 함께 발견됨.


환경

  • Portal: Express + JWT 기반 인증
  • 하위 서비스: Express, 독립적인 auth middleware 사용
  • 구조: Portal에서 JWT 발급 → 하위 서비스에서 토큰 검증 후 권한 기반 데이터 반환

원인 분석

1. JWT 토큰 구조 불일치

Portal에서 발급하는 JWT 토큰:

1
2
3
4
5
6
// Portal JWT payload
{
  sub: userId,
  tenantId: "tenant-001",
  roles: ["super_admin", "service_manager"]  // 배열
}

하위 서비스의 auth middleware:

1
2
3
4
5
6
// 하위 서비스 auth middleware
req.user = {
  role: decoded.role,  // ❌ 단수 'role'로 접근 → undefined
  tenantId: decoded.tenantId,
  userId: decoded.sub
};

roles(복수)로 보내는데 role(단수)로 읽고 있었음. 당연히 undefined.

2. Controller의 role 기반 분기 문제

1
2
3
4
5
6
7
8
// controller
switch (role) {
  case 'companies_admin':   // Portal에 없는 role
  case 'company_admin':     // Portal에 없는 role
    return getAllData();
  default:
    return { items: [] };   // 여기로 빠짐
}

Portal에서 사용하는 role 체계(super_admin, service_manager)와 하위 서비스에서 기대하는 role 체계(companies_admin, company_admin)가 완전히 달랐음.

결과적으로:

  1. rolesrole로 읽어서 undefined
  2. undefined는 어떤 case에도 매칭 안 됨
  3. default로 빠져서 빈 배열 반환

해결 방법

Before

1
2
3
4
5
6
7
// auth middleware
const decoded = jwt.verify(token, secret);
req.user = {
  role: decoded.role,  // undefined
  tenantId: decoded.tenantId,
  userId: decoded.sub
};

After

1
2
3
4
5
6
7
8
9
10
// auth middleware
const decoded = jwt.verify(token, secret);
const roles = decoded.roles || [];

req.user = {
  roles: roles,
  role: roles[0] || null,
  tenantId: decoded.tenantId,
  userId: decoded.sub
};
1
2
3
4
5
6
7
8
9
10
11
12
// controller
const { roles } = req.user;

const hasAdminAccess = roles.some(r =>
  ['super_admin', 'portal_admin', 'companies_admin', 'company_admin'].includes(r)
);

if (hasAdminAccess) {
  return getAllData();
}

return getDataByRole(roles);

핵심 변경:

  • role(단수) → roles(배열) 정상 매핑
  • switch 분기 대신 Array.some()으로 role 포함 여부 체크
  • Portal role 체계와 레거시 role 체계 모두 허용

결과

  • 하위 서비스 데이터 정상 조회 확인
  • Portal admin 계정으로 전체 데이터 접근 가능
  • 기존 레거시 role도 하위 호환 유지

정리

  • JWT 토큰의 claim 이름은 발급 측과 검증 측이 정확히 일치해야 함. role vs roles 같은 단수/복수 차이도 치명적임.
  • 멀티 서비스 구조에서 각 서비스가 독립적으로 auth를 구현하면 이런 불일치가 생기기 쉬움. 공통 auth 미들웨어를 shared 패키지로 관리하는 게 맞음.
  • switch로 role을 하드코딩하는 패턴은 role 체계가 바뀔 때마다 깨짐. 허용 role 목록을 설정으로 분리하는 게 유지보수에 유리함.
This post is licensed under CC BY 4.0 by the author.