JWT 가이드

Secret 없이 JWT를 디코딩할 때 제가 확인하는 것

JWT는 secret 없이도 header와 payload를 읽을 수 있습니다. 제가 디코딩, 서명 검증, claim 검증을 어떻게 분리하고 안전하게 디버깅하는지 단계별로 설명합니다.

작성: JWTDecoder.tools마지막 확인 2026-09-13

편집 문체 안내: 1인칭 표현은 JWTDecoder.tools의 문서화된 검증 워크플로를 뜻하며, 게시자와 조직 저자는 JWTDecoder.tools입니다.

secret 없이 JWT를 디코딩한 뒤 서명 검증 전에서 멈추는 흐름
먼저 디코딩하고 확인한 다음 신뢰할 수 있는 키로 검증한 뒤 신뢰 여부를 판단합니다.

처음 JWT를 접했을 때 가장 많이 생기는 오해 중 하나는 secret이 없으면 token 내용을 볼 수 없다는 생각입니다. 일반적인 서명형 JWT(JWS)는 그렇지 않습니다. header와 payload는 암호화된 것이 아니라 Base64URL로 인코딩되어 있기 때문에 secret이나 private key가 없어도 읽을 수 있습니다.

제가 JWT Decoder에서 token을 열어보는 이유도 바로 이것입니다. 먼저 보이는 정보를 읽고 문제 범위를 좁힙니다. 하지만 여기서 멈추면 안 됩니다. 디코딩은 검증이 아닙니다.

Secret 없이 무엇을 볼 수 있는가

3-part signed JWT는 보통 다음 구조입니다.

header.payload.signature

앞의 두 segment를 Base64URL로 디코딩하면 JSON header와 payload를 읽을 수 있습니다.

예를 들어 header에는 다음과 같은 값이 있을 수 있습니다.

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "2026-09-key-a"
}

payload에는:

{
  "iss": "https://issuer.example.com/",
  "aud": "https://api.example.com",
  "sub": "user-123",
  "iat": 1789320000,
  "exp": 1789323600
}

이 값들은 secret 없이 읽을 수 있습니다. 이것은 보안 취약점이 아니라 JWT의 일반적인 설계 특성입니다. 따라서 저는 payload에 password나 private key 같은 비밀을 넣지 않습니다.

제가 디코딩과 검증을 세 단계로 나누는 이유

JWT를 다룰 때 저는 상태를 세 가지로 구분합니다.

1. Decoded

header와 payload를 파싱할 수 있는 상태입니다. 여기서 확인할 수 있는 것은 “token이 이렇게 주장한다” 정도입니다.

2. Signature verified

신뢰하는 secret, public key 또는 JWKS를 사용해서 signature가 실제로 맞는지 확인한 상태입니다.

3. Application accepted

서명이 맞더라도 iss, aud, exp, nbf, token type, scope/role이 애플리케이션 정책과 맞아야 최종적으로 허용합니다.

이 세 단계를 한 문장으로 합치면 디버깅이 어려워집니다. 예를 들어 “JWT가 valid하다”는 말만으로는 JSON이 파싱됐다는 뜻인지, 서명이 맞았다는 뜻인지, API가 받아야 한다는 뜻인지 알 수 없습니다.

Secret 없이 디코딩할 때 제가 먼저 보는 header 값

alg

alg는 token이 어떤 signing algorithm을 사용했다고 주장하는지 보여줍니다. 저는 이 값을 참고하지만 trust policy로 사용하지 않습니다. 서버는 미리 허용한 algorithm allowlist를 가져야 합니다.

kid

kid는 여러 signing key 중 어떤 key를 사용했는지 선택하는 식별자입니다. 특히 RS256/ES256 환경에서는 JWKS의 key를 찾는 데 중요합니다.

하지만 kid가 JWKS 안에 존재한다고 해서 signature가 자동으로 검증된 것은 아닙니다. JWKS Validator에서 key 선택과 실제 signature verification을 분리해서 확인합니다.

typ

typ는 token 유형을 설명하는 힌트가 될 수 있지만 이것만으로 access token과 ID token을 안전하게 구분한다고 가정하지 않습니다. 실제 verifier 정책이 더 중요합니다.

Payload에서 제가 먼저 보는 claim

iss

누가 token을 발급했다고 주장하는지 보여줍니다. 신뢰 판단에서는 exact issuer matching이 중요합니다.

aud

어떤 resource/API를 대상으로 발급됐는지 나타냅니다. JWT가 정상적으로 서명됐더라도 제 API가 audience가 아니면 거부할 수 있습니다.

exp

만료 시각입니다. JWT NumericDate는 초 단위입니다. 만료 여부만 빠르게 보려면 JWT Expiry Checker를 사용합니다.

nbf

이 시각 전에는 token을 사용할 수 없다는 의미입니다. 시스템 시계 차이와 작은 clock skew 정책을 함께 확인합니다.

iat

발급 시각입니다. exp - iat를 계산하면 token lifetime이 비정상적으로 긴지 판단하는 데 도움이 됩니다.

제가 secret 없이 디코딩하는 실제 흐름

  1. production token 대신 가능하면 synthetic/redacted token을 준비합니다.
  2. Bearer 접두어가 있으면 제거합니다.
  3. segment가 3개인지 확인합니다.
  4. header를 읽고 alg, kid를 확인합니다.
  5. payload를 읽고 iss, aud, exp, nbf, iat를 확인합니다.
  6. 이 단계의 결과를 decoded only라고 명확히 표시합니다.
  7. signature trust가 필요하면 적절한 verifier로 이동합니다.
  8. signature가 맞더라도 claim policy를 별도로 검증합니다.

왜 secret이 없어도 디코딩할 수 있는가

Base64URL은 암호화가 아닙니다. bytes를 URL-safe한 문자열로 표현하는 encoding입니다. 누구든지 다시 원래 bytes로 변환할 수 있습니다.

서명은 다른 역할을 합니다. signature는 header와 payload가 signing 이후 변조되지 않았는지, 그리고 신뢰하는 key를 가진 주체가 서명했는지를 확인하기 위해 사용합니다.

즉:

Base64URL decode → 내용을 읽는다
Signature verify → 누가 서명했는지/변조되지 않았는지 확인한다
Claim validation → 내 애플리케이션이 받아도 되는지 확인한다

HS256에서는 무엇이 달라지는가

HS256은 shared secret을 사용합니다. payload를 읽는 데 secret은 필요하지 않지만 signature를 검증하려면 같은 secret이 필요합니다.

이 때문에 저는 온라인 디버깅 도구에 production HS256 secret을 붙여넣지 않습니다. 브라우저 로컬 도구라고 해도 synthetic secret을 우선 사용합니다.

HS256과 RS256의 trust 모델 차이는 HS256 vs RS256 가이드에서 더 자세히 설명합니다.

RS256/ES256에서는 무엇이 달라지는가

비대칭 방식에서는 verifier가 private key를 가질 필요가 없습니다. issuer가 공개한 public key/JWKS로 signature를 검증할 수 있습니다.

저는 보통 다음 순서를 따릅니다.

trusted issuer
  ↓
trusted JWKS endpoint
  ↓
JWT header의 kid
  ↓
matching JWK
  ↓
algorithm/key compatibility
  ↓
signature verify
  ↓
issuer/audience/time checks

이 흐름을 확인하려면 JWKS Validator를 사용합니다.

제가 절대 하지 않는 것

  • payload가 잘 읽힌다는 이유로 인증을 통과시키지 않습니다.
  • token이 주장한 alg를 그대로 보안 정책으로 사용하지 않습니다.
  • token 안의 jku/x5u URL을 자동으로 따라가지 않습니다.
  • production access token 전체를 로그나 URL에 남기지 않습니다.
  • 디버깅 convenience 때문에 signature verification을 끄지 않습니다.

특히 “signature를 끄면 문제는 사라진다”는 접근은 원인을 해결하는 것이 아니라 trust boundary를 제거하는 것입니다.

Secret 없이 디코딩한 뒤 signature를 검증해야 하는 경우

다음 질문 중 하나라도 “예”라면 decode-only로 끝내지 않습니다.

  • 이 token을 인증에 사용할 것인가?
  • 이 claim을 authorization 결정에 사용할 것인가?
  • issuer가 실제로 이 token을 발급했다는 것을 증명해야 하는가?
  • token이 변조되지 않았는지 확인해야 하는가?

그때는 JWT Signature Verifier나 JWKS Validator로 넘어갑니다.

제 최종 체크리스트

제가 secret 없이 JWT를 볼 때는 다음 문장을 기준으로 판단합니다.

“나는 이 token이 무엇을 주장하는지 읽었다. 아직 그 주장을 신뢰한 것은 아니다.”

그 다음 단계에서 signature를 검증하고, 마지막으로 issuer·audience·시간·권한 정책을 적용합니다. 이 세 단계를 분리하면 JWT 문제를 훨씬 빠르게 디버깅할 수 있고, 단순한 decoder가 인증 시스템처럼 사용되는 실수도 막을 수 있습니다.

출처 및 참고자료

관련 가이드