ES /docs

Cognito get_user_by_access_token failed: Access token decoded but payload is empty, #<Cupix::Auth::V

RCA: Cognito get_user_by_access_token failed: Access token decoded but payload is empty

Error Log#

Datadog Logs

text
Cognito get_user_by_access_token failed: Access token decoded but payload is empty, #<Cupix::Auth::VerifiedAccessToken:0x00007fcb7c6e7ea0 @access_token="eyJhbGciOiJIUzI1NiJ9.eyJ0b2tlbiI6Im5jcXBkdjdnNzAzbiIsImlzcyI6Imh0dHBzOi8vZmxpbnRjby5jdXBpeC53b3JrcyIsImlhdCI6MTc3NTU4OTE0MSwiZXhwIjoxNzc1NTkyNzQxLCJ0b2tlbl90eXBlIjoiYWNjZXNzX3Rva2VuIiwic2NvcGUiOm51bGx9.t7KPbjgXLs0bshRI7-HBgDdmigBmfUU1Yna4w6eNKUg", @decoded_access_token=nil, @issuer=nil>

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 1 (이 클러스터), 48시간 내 동일 패턴 4건
  • 최초 발생: 2026-04-07T19:12:33.575Z
  • 최근 발생: 2026-04-07T19:12:33.575Z

Root Cause Summary#

Cross-region 인증 요청에서 JWT issuer 불일치로 인해 토큰 검증이 실패합니다. 토큰은 US 서버($APP_HOST=cupix.works)에서 발급되어 issuer가 https://flintco.cupix.works로 설정되었으나, EU 서버($APP_HOST=cupix-eu.works)에서 인증을 시도할 때 Cupixworks.match? regex가 cupix-eu.works 패턴만 허용하므로 매칭에 실패합니다. Cognito issuer 패턴도 매칭되지 않아, verify_jwt_access_token!decoded_access_token=nil, issuer=nilVerifiedAccessToken을 반환하고, 이후 "Access token decoded but payload is empty" 에러가 발생합니다.

Technical Analysis#

Code Path#

  • Entry point: lib/cupix/auth/verification.rb:20verify_authenticated_request!
  • JWT 유효성 체크: lib/cupix/auth/verification.rb:45Cupix::Auth::Jwt.valid_jwt? 호출로 JWT 포맷 확인
  • 핵심 검증: lib/cupix/auth/verification.rb:55verify_jwt_access_token! 호출
ruby
# lib/cupix/auth/verification.rb:208-234
def self.verify_jwt_access_token!(access_token: nil)
  jwt = JWT.decode(access_token, nil, false)
  iss = jwt[0]['iss']  # => "https://flintco.cupix.works"
  # ...
  registered_issuers = [Cupix::Auth::Issuers::Cognito, Cupix::Auth::Issuers::Cupixworks]
  decoded_token = nil
  _issuer = nil

  registered_issuers.each do |issuer|
    next unless issuer.match?(iss)  # 두 issuer 모두 매칭 실패 → skip
    decoded_token = issuer.verify!(access_token)
    _issuer = issuer
  end

  # decoded_token=nil, _issuer=nil 상태로 반환
  ::Cupix::Auth::VerifiedAccessToken.new(access_token: access_token, decoded_access_token: decoded_token, issuer: _issuer)
end
  • Issuer 매칭 실패 원인 — Cupixworks.match?:
ruby
# lib/cupix/auth/issuers/cupixworks.rb:94-101
def match?(issuer)
  protocol = Rails.env.development? ? 'https?' : 'https'
  regex_pattern = "^#{protocol}://[a-z0-9-]+\.#{Regexp.escape($APP_HOST)}$"
  regex = Regexp.new(regex_pattern, Regexp::IGNORECASE)
  !!issuer.match(regex)
end
# EU 서버에서 $APP_HOST='cupix-eu.works' → regex: ^https://[a-z0-9-]+\.cupix\-eu\.works$
# issuer 'https://flintco.cupix.works'는 이 패턴에 매칭 안 됨
  • Cognito.match?도 당연히 실패:
ruby
# lib/cupix/auth/issuers/cognito.rb:38-41
def match?(issuer)
  regex = %r{^https://cognito-idp\.[a-z0-9-]+\.amazonaws\.com/[a-z0-9-_]+$}i
  !!issuer.match(regex)
end
# 'https://flintco.cupix.works'는 Cognito 패턴 아님
  • Failure point: lib/cupix/auth/verification.rb:63-68decoded_access_token.nil? 체크에서 에러 발생
ruby
# lib/cupix/auth/verification.rb:61-69
begin
  if verified_access_token.decoded_access_token.nil?
    raise Cupix::Errors::Unauthorized.new(
      code: 'AUTH20008',
      reason: 'Access token decoded but payload is empty',
      access_token: verified_access_token.access_token
    )
  end
  • 에러가 rescue에서 잡혀 로깅됨: lib/cupix/auth/verification.rb:82-88
ruby
# lib/cupix/auth/verification.rb:82-88
rescue StandardError => e
  Cupix::Logger.error("Cognito get_user_by_access_token failed: #{e.message}, #{verified_access_token.inspect}",
                      class: 'Verification',
                      function: __method__,
                      module: 'Cupix::Auth',
                      error_type: e.class.name,
                      decoded_token_present: !verified_access_token.decoded_access_token.nil?)
  • 토큰 발급 시 issuer 결정 로직 — $APP_HOST 사용:
ruby
# app/models/concerns/properties/team.rb:21-23
def app_url
  "#{$APP_PROTOCOL}://#{self.domain}.#{$APP_HOST}"
end
ruby
# lib/cupix/auth/jwt.rb:23-36
def issuer(any_entity)
  Rails.cache.fetch("cupix:auth:jwt:issuer:#{any_entity.class.name}:#{any_entity.id}", expires_in: 1.week) do
    case any_entity.class.name
    when 'Team'
      any_entity.app_url  # => "https://flintco.cupix.works" (US 서버에서)
    # ...
    end
  end
end

기대 동작: JWT의 iss 클레임이 인증 서버의 $APP_HOST와 매칭되어 Cupixworks issuer가 토큰을 검증하고, signin! 흐름으로 진행.

실제 동작: Cross-region 요청에서 토큰의 issuer 도메인(cupix.works)과 서버의 $APP_HOST(cupix-eu.works)가 불일치하여, 어떤 registered issuer도 매칭되지 않고, VerifiedAccessTokennil 값으로 반환됨.

Log Evidence#

48시간 범위에서 동일 패턴 4건 확인 — 모두 cross-region 인증 시도:

text
Datadog query: service:cupixworks-api "get_user_by_access_token"
Time range: 2026-04-06T00:00:00Z to 2026-04-08T00:00:00Z
Timestamp (UTC) Host Region JWT Issuer (tenant domain) Token
2026-04-07T19:12:33.575Z eu-central-1 https://flintco.cupix.works ncqpdv7g703n
2026-04-06T05:49:08.305Z eu-central-1 https://tokiomarinehd.cupix-jp.works txh82brvmsoe
2026-04-06T05:43:11.461Z ap-northeast-1 https://byuk.cupix-eu.works v1e4tai27eeu
2026-04-06T05:42:25.823Z eu-central-1 https://structon.cupix-sg.works obfgsbdcper5

모든 로그의 공통 속성:

json
{
  "class": "Verification",
  "function": "verify_authenticated_request!",
  "module": "Cupix::Auth",
  "error_type": "Cupix::Errors::Unauthorized",
  "decoded_token_present": false
}

패턴 분석:

  • flintco.cupix.works (US 도메인) → EU 서버에서 실패 ($APP_HOST=cupix-eu.works)
  • tokiomarinehd.cupix-jp.works (JP 도메인) → EU 서버에서 실패
  • byuk.cupix-eu.works (EU 도메인) → JP 서버에서 실패 ($APP_HOST=cupix-jp.works)
  • structon.cupix-sg.works (SG 도메인) → EU 서버에서 실패

모든 실패는 토큰 발급 region과 인증 시도 region이 다른 경우에 발생합니다.

$APP_HOST 설정 (config/environments/production.rb:138):

ruby
$APP_HOST = ENV['APP_HOST'] || 'cupix.works'

region별 배포에서 APP_HOST 환경 변수가 각각 다르게 설정됨:

  • US: cupix.works
  • EU: cupix-eu.works
  • JP: cupix-jp.works
  • SG: cupix-sg.works

Fix Recommendation#

즉시 조치 (Critical)#

Cupixworks.match? 메서드(lib/cupix/auth/issuers/cupixworks.rb:94-101)를 수정하여 모든 region의 도메인을 인식하도록 변경해야 합니다.

현재 $APP_HOST 하나만 사용하여 매칭하지만, 모든 유효한 Cupix 도메인(cupix.works, cupix-eu.works, cupix-jp.works, cupix-sg.works, cupix-ca.works)을 허용하는 allowlist 기반 매칭이 필요합니다.

수정 대상: lib/cupix/auth/issuers/cupixworks.rb:94-101

단기 개선 (1주 이내)#

  • verify_jwt_access_token!(lib/cupix/auth/verification.rb:221-230)에서 어떤 issuer도 매칭되지 않았을 때 명시적으로 에러를 발생시켜야 합니다. 현재는 nil 값이 silent하게 반환되어 에러 메시지가 "payload is empty"로 표시되므로, 실제 원인(issuer 불일치)을 숨기게 됩니다.
  • 에러 로그 메시지(lib/cupix/auth/verification.rb:83)도 "Cognito get_user_by_access_token failed"가 아닌 실제 실패 원인을 반영하도록 개선 필요 — Cognito와 무관한 토큰에 대해서도 이 메시지가 출력되어 혼동을 줍니다.

장기 개선 (재발 방지)#

  • Multi-region 토큰 검증 아키텍처 재설계: 토큰의 iss 클레임을 기반으로 region 판별 후 적절한 secret으로 서명 검증하는 방식으로 전환
  • Region-aware issuer registry를 도입하여 모든 유효한 Cupix 도메인을 중앙에서 관리
  • JWT issuer 캐시(cupix:auth:jwt:issuer:*)가 region 이동 시에도 올바르게 동작하는지 검증

Monitoring#

  • Cross-region 인증 실패를 추적하는 메트릭 추가:
text
service:cupixworks-api "Access token decoded but payload is empty" | count by @host
  • Issuer 불일치 발생 시 별도 경고 알림 설정:
text
service:cupixworks-api "get_user_by_access_token failed" @decoded_token_present:false | count > 5 in 1h

Risk Assessment#

  • Risk level: medium
  • 예상 복잡도: standard — Cupixworks.match? 수정은 비교적 단순하나, 보안 영향(유효하지 않은 도메인 허용 가능성)을 신중히 검토해야 합니다. Cross-region 인증이 의도된 동작인지 비즈니스 확인이 필요합니다.