JWT payload를 열면 여러 claim이 보입니다. 저는 claim을 볼 때 단순히 “무슨 값이 들어 있는가”보다 이 값이 정보인지, trust policy인지, authorization 입력인지를 구분합니다.
JWT Decoder로 token을 열었을 때 보이는 claim은 아직 검증 전 데이터입니다. 그래서 먼저 읽고, 그 다음 signature를 검증하고, 마지막으로 애플리케이션 정책에 맞는지 확인합니다.
JWT claim의 세 가지 범주
RFC 7519 관점에서 claim 이름은 크게 registered, public, private 영역으로 이해할 수 있습니다.
- Registered claims:
iss,sub,aud,exp,nbf,iat,jti처럼 표준에 정의된 이름 - Public claims: 충돌을 피하도록 공개적으로 정의된 이름
- Private claims: 특정 시스템 사이에서 합의한 custom claim
저는 custom claim 자체를 문제라고 보지 않습니다. 다만 role, permissions, tenant_id 같은 값이 authorization에 사용된다면 signature와 issuer/audience 검증을 통과한 뒤에만 신뢰합니다.
iss — issuer
iss는 token을 발급했다고 주장하는 주체입니다.
{ "iss": "https://id.example.com/" }
제가 확인하는 것은:
- expected issuer와 정확히 일치하는가
- 이 issuer와 연결된 trusted key/JWKS를 쓰고 있는가
- 환경별 issuer가 섞이지 않았는가
issuer 문자열은 단순 label이 아니라 trust chain의 시작점입니다. 자세한 흐름은 JWT Issuer Validation 가이드에서 다룹니다.
sub — subject
sub는 token의 주체를 식별합니다. 보통 user ID나 service principal 같은 값을 나타낼 수 있습니다.
중요한 점은 sub 의미가 issuer context 안에서 해석되어야 한다는 것입니다. 서로 다른 issuer가 같은 sub 문자열을 쓸 수 있기 때문에 저는 전역 사용자 ID처럼 무조건 취급하지 않습니다.
aud — audience
aud는 이 token을 소비하도록 의도된 대상을 나타냅니다.
{ "aud": "https://api.example.com" }
또는 배열일 수 있습니다.
{ "aud": ["api-a", "api-b"] }
API는 자신의 expected audience가 포함되어 있는지 확인해야 합니다. signature가 맞아도 다른 resource용 token이면 거부해야 합니다.
문제가 있다면 JWT Audience Mismatch 가이드를 확인합니다.
exp — expiration time
exp 이후에는 token을 받아들이면 안 됩니다.
{ "exp": 1789323600 }
JWT NumericDate는 Unix epoch 기준 초입니다. JavaScript의 Date.now()는 밀리초이므로 단위를 섞지 않습니다.
시간 상태만 빠르게 보고 싶을 때는 JWT Expiry Checker를 사용합니다.
nbf — not before
nbf 이전에는 token이 아직 활성화되지 않은 것으로 봅니다.
{ "nbf": 1789320000 }
분산 시스템에서는 약간의 clock skew가 있을 수 있으므로 verifier가 작은 tolerance를 둘 수 있습니다. 하지만 큰 차이를 무조건 허용하는 방식으로 문제를 숨기지는 않습니다.
iat — issued at
iat는 token 발급 시각입니다.
제가 주로 활용하는 방식은:
- token age 계산
exp - iat으로 lifetime 확인- 미래 시각으로 발급된 token 탐지
- rotation/incident timeline 분석
iat 하나만으로 token을 신뢰하지는 않습니다.
jti — JWT ID
jti는 token에 고유 식별자를 부여하는 데 사용할 수 있습니다.
시스템에 따라 replay 방지나 revocation 목록에 활용할 수 있지만 jti가 있다고 replay가 자동으로 방지되는 것은 아닙니다. 실제 서버 저장/검사 정책이 필요합니다.
Custom scope와 role claim
OAuth access token에서 scope가 문자열로 들어갈 수 있고, provider마다 roles, permissions, groups 같은 custom claim을 사용할 수 있습니다.
저는 authorization code에서 다음을 확인합니다.
- 이 claim이 trusted issuer에서 온 token인가
- signature가 검증됐는가
- audience가 현재 API인가
- claim의 타입과 형식이 예상대로인가
- 필요한 최소 권한만 허용하는가
payload를 디코딩했다는 이유만으로 role을 신뢰하면 안 됩니다.
Claim이 없으면 어떻게 하는가
모든 registered claim이 필수는 아닙니다. 어떤 claim이 필요한지는 token profile과 애플리케이션 정책에 따라 달라집니다.
예를 들어 API가 expiration을 반드시 요구한다면 exp가 없는 token을 거부하도록 정책을 설정할 수 있습니다. issuer 또는 audience도 마찬가지입니다.
제가 피하는 것은 “표준상 optional이니까 언제나 없어도 된다” 또는 “registered claim이니까 언제나 필수다”라는 양 극단입니다.
Claim 타입도 검증합니다
다음처럼 값은 존재하지만 타입이 예상과 다를 수 있습니다.
{ "exp": "1789323600" }
exp가 문자열이면 제가 기대하는 NumericDate number가 아닙니다.
aud도 string 또는 array라는 허용 형태를 처리해야 합니다. custom claim도 schema가 정해져 있다면 타입을 검증합니다.
디코딩과 claim validation을 구분합니다
JWT Validator는 구조와 시간 claim을 확인하는 데 도움이 됩니다. 하지만 애플리케이션별 issuer/audience/scope 규칙은 별도 policy가 필요합니다.
제가 상태를 나누는 방식은 다음과 같습니다.
Decoded
→ header/payload를 읽음
Signature verified
→ trusted key로 서명 확인
Claims validated
→ iss/aud/time/type 정책 확인
Authorized
→ scope/role로 현재 작업 허용 여부 확인
한 단계가 다음 단계를 자동으로 보장하지 않습니다.
ID token과 access token의 claim을 같은 규칙으로 보지 않습니다
둘 다 JWT일 수 있지만 token profile이 다릅니다. OIDC ID Token에는 client와 authentication에 관련된 claim이 있고, access token은 resource API를 위한 정보가 중심일 수 있습니다.
저는 token type을 먼저 확인하고 해당 protocol/profile의 validation 규칙을 적용합니다.
제가 claim 디버깅할 때 보는 순서
iss— 예상 issuer인가aud— 현재 API용인가exp— 만료됐는가nbf— 아직 너무 이른가iat— 발급 시각이 합리적인가sub— 예상 주체 형식인가- token-specific claims — scope/role/type 등이 맞는가
jti— 시스템이 실제로 사용하는 경우 uniqueness/replay 정책과 맞는가
401 문제 전체를 추적한다면 JWT Debugger와 JWT 401 Unauthorized 가이드를 함께 봅니다.
Claim을 로그에 남길 때 주의합니다
JWT payload가 암호화되지 않았더라도 개인정보나 내부 identifier가 들어 있을 수 있습니다. 그래서 production 로그에 payload 전체를 그대로 남기지 않습니다.
필요한 진단 정보만 선택적으로 기록하고 access token 원문은 저장하지 않는 편을 선호합니다.
제 최종 원칙
JWT claim을 볼 때 저는 다음 문장을 기억합니다.
Claim은 token이 하는 주장이고, signature와 application policy가 그 주장을 신뢰할지 결정한다.
iss, aud, exp 같은 값은 매우 중요하지만 디코딩 화면에 보인다는 사실만으로 신뢰할 수 없습니다. trusted key로 signature를 검증하고, expected issuer/audience/time policy를 적용한 뒤에야 그 claim을 인증이나 authorization 결정에 사용할 수 있습니다.