JWT 가이드

JWT alg=none을 제가 어떻게 해석하고 방어하는가

JWT header의 alg=none은 unsecured JWS를 의미합니다. 왜 위험하게 오해될 수 있는지, 어떤 경우에 거부해야 하는지, verifier allowlist와 token type 분리를 어떻게 적용하는지 설명합니다.

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

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

alg=none ∉ application allowlist
token alg → application allowlist → accept / reject

JWT header에서 "alg": "none"을 보면 저는 이것을 “특별한 서명 알고리즘”으로 생각하지 않습니다. none은 암호학적 서명이 없는 unsecured JWS를 나타냅니다. 그래서 인증이나 authorization에 사용되는 JWT에서 예상하지 않았다면 저는 즉시 거부 대상으로 봅니다.

중요한 포인트는 alg=none 자체를 공포 키워드로 다루는 것이 아니라 verifier가 어떤 알고리즘을 허용하도록 설계되어 있는가입니다.

alg=none은 무엇을 의미하는가

일반적인 signed JWT/JWS는 다음처럼 보입니다.

header.payload.signature

alg=none을 사용하는 unsecured 형태에서는 signature가 비어 있을 수 있습니다.

예:

{
  "alg": "none",
  "typ": "JWT"
}

compact form은 개념적으로 다음처럼 끝납니다.

eyJ...eyJ....

마지막 점 뒤에 signature bytes가 없습니다.

이 token은 내용 자체는 읽을 수 있지만 누가 만들었는지 cryptographic signature로 증명하지 않습니다.

왜 문제가 되는가

문제는 verifier가 미검증 header의 alg를 보고 “token이 none이라고 했으니 signature 검증을 하지 않겠다”고 결정할 때 생깁니다.

나쁜 흐름은 이렇습니다.

JWT header를 읽음
→ alg가 none
→ token 요청대로 verification 생략
→ payload를 신뢰

공격자는 payload와 header를 직접 만들 수 있으므로 이런 verifier에서는 원하는 claim을 넣은 token이 받아들여질 수 있습니다.

제가 원하는 흐름은 반대입니다.

애플리케이션 policy가 허용 알고리즘을 먼저 결정
→ JWT header의 alg가 allowlist에 있는지 확인
→ 허용되지 않으면 즉시 거부
→ 허용된 알고리즘으로 signature 검증

제가 algorithm allowlist를 먼저 두는 이유

예를 들어 API가 RS256 access token만 받아야 한다면 verifier 설정은 개념적으로 다음과 같습니다.

allowed algorithms = ["RS256"]

이때 token이:

{ "alg": "none" }

또는:

{ "alg": "HS256" }

이라고 주장해도 verifier는 정책을 바꾸지 않습니다.

header는 아직 검증되지 않은 입력이므로 “무엇을 사용해야 하는지” 알려주는 신뢰 가능한 configuration source가 아닙니다.

alg=none을 무조건 모든 곳에서 금지해야 하는가

일부 표준/프로토콜 내부에서 unsecured JWT 사용 사례가 정의될 수는 있습니다. 하지만 제가 일반적인 API authentication 시스템을 설계할 때는 명시적인 요구가 없는 한 허용하지 않습니다.

중요한 것은 context입니다.

  • 이 endpoint가 signed access token을 요구하는가?
  • 프로토콜 사양이 unsecured JWT를 명시적으로 허용하는가?
  • 해당 token이 authentication/authorization 결정에 사용되는가?
  • 별도의 보호 채널이나 integrity mechanism이 존재하는가?

이 질문에 대한 명확한 설계가 없다면 none을 허용할 이유가 없습니다.

제가 alg=none token을 발견했을 때 확인하는 순서

  1. JWT Decoder에서 header와 payload를 읽습니다.
  2. 원본 token segment 수와 빈 signature를 확인합니다.
  3. 애플리케이션이 허용하는 algorithm 목록을 확인합니다.
  4. 이 token이 access token, ID token, 내부 assertion 중 무엇인지 확인합니다.
  5. issuer 문서가 실제로 이 형태를 허용하는지 확인합니다.
  6. 인증/authorization 경로에서 사용된다면 일반적으로 거부하도록 verifier를 고칩니다.
  7. 테스트를 추가해 alg=none이 다시 허용되지 않도록 합니다.

JWT Security Analyzer에서 alg=none, empty signature, unexpected algorithm을 빠르게 확인할 수 있습니다.

“디코딩이 되는데 왜 invalid인가?”

alg=none token도 header와 payload는 Base64URL JSON이므로 정상적으로 디코딩될 수 있습니다.

이것은 모순이 아닙니다.

Decoded = 내용을 읽음
Verified = 신뢰하는 key로 서명을 확인함
Accepted = 애플리케이션 정책까지 통과함

alg=none token은 첫 번째 상태에는 도달할 수 있지만 signed token을 요구하는 시스템에서는 두 번째 상태를 만족하지 않습니다.

Signature segment가 비어 있으면 무조건 none인가

아닙니다. header의 alg와 전체 구조를 함께 봅니다.

예를 들어 alg=RS256인데 signature가 비어 있다면 malformed/invalid token으로 보는 것이 자연스럽습니다. verifier가 empty signature를 none처럼 처리해서는 안 됩니다.

저는 다음 두 경우를 분리합니다.

alg=none + empty signature
→ unsecured JWT/JWS 형태

alg=RS256 + empty signature
→ signed JWT 요구사항을 충족하지 못하는 invalid token

Algorithm confusion과 같은 문제인가

관련은 있지만 동일한 문제는 아닙니다.

algorithm confusion은 verifier가 서로 다른 algorithm family를 잘못 취급하면서 key semantics가 뒤섞이는 문제를 포함할 수 있습니다. alg=none 문제는 signature 자체를 건너뛰도록 만드는 잘못된 정책이 핵심입니다.

공통 방어 원칙은 같습니다.

검증 알고리즘은 미검증 token이 아니라 verifier의 고정된 policy가 결정해야 합니다.

라이브러리를 사용할 때 제가 확인하는 설정

JWT 라이브러리를 선택했다고 자동으로 안전해지는 것은 아닙니다. 저는 다음을 확인합니다.

  • 허용 algorithm을 명시할 수 있는가?
  • none이 기본적으로 거부되는가?
  • key type과 algorithm이 올바르게 연결되는가?
  • issuer와 audience 검증을 함께 설정할 수 있는가?
  • malformed/empty signature에 fail closed 하는가?

그리고 테스트 fixture를 추가합니다.

valid RS256 token → pass
RS256 wrong signature → fail
alg=none → fail
HS256 when only RS256 allowed → fail
expired token → fail
wrong issuer → fail
wrong audience → fail

이런 negative test가 실제 보안 설정을 더 명확하게 보여줍니다.

kid, jku, x5u와 함께 볼 때

algorithm 정책만 안전해도 끝은 아닙니다. token header에는 key 선택에 영향을 줄 수 있는 다른 값도 있습니다.

  • kid
  • jku
  • x5u

저는 token이 지정한 remote URL을 자동으로 따라가지 않습니다. trusted issuer와 trusted metadata에서 JWKS source를 결정한 뒤 그 key set 안에서 kid를 사용합니다.

즉:

trusted issuer
→ trusted JWKS
→ allowed algorithm
→ compatible key
→ signature verify

이 흐름을 확인할 때는 JWKS Validator를 사용합니다.

여러 token type을 한 verifier에서 받지 않는 이유

RFC 8725의 best practice 중 하나는 서로 다른 JWT 사용 목적을 명확하게 분리하는 것입니다.

예를 들어:

  • ID token
  • API access token
  • email verification token
  • password reset token

이들이 같은 key와 같은 validation rules를 무분별하게 공유하면 한 종류의 token이 다른 context에서 받아들여질 위험이 있습니다.

저는 가능하면 issuer/audience/type/algorithm policy를 endpoint 목적에 맞게 분리합니다.

제가 피하는 잘못된 수정

“none token만 특별히 if문으로 막자”

필요한 방어이지만 근본적으로는 allowlist가 더 중요합니다. 앞으로 다른 unexpected algorithm이 들어와도 정책이 흔들리지 않아야 합니다.

“signature가 없으니 개발 환경에서만 받아보자”

개발용 우회가 production configuration으로 새어 나가기 쉽습니다. synthetic token과 별도 test path를 사용합니다.

“payload 내용이 합리적이니까 괜찮다”

공격자가 payload를 직접 만들 수 있으므로 내용의 그럴듯함은 trust 근거가 아닙니다.

제 최종 방어 체크리스트

  1. endpoint가 허용하는 algorithm을 명시적으로 정의합니다.
  2. token header가 policy를 바꾸지 못하게 합니다.
  3. alg=none은 명시적 사양 요구가 없다면 거부합니다.
  4. signed token에서 empty signature를 거부합니다.
  5. algorithm과 key type compatibility를 확인합니다.
  6. trusted issuer에 연결된 key source만 사용합니다.
  7. signature 검증 뒤 issuer/audience/time/token type을 검증합니다.
  8. alg=none과 unexpected algorithm에 대한 negative test를 유지합니다.

제가 alg=none을 볼 때의 핵심 판단은 단순합니다. 이 token이 서명 없는 데이터를 허용하도록 설계된 명확한 protocol context가 있는가? 일반적인 API authentication에서는 그렇지 않으므로, verifier policy가 none을 선택할 여지를 주지 않는 것이 가장 안전한 출발점입니다.

출처 및 참고자료

관련 가이드