JWT 토큰 디코딩 방법: 개발자를 위한 완벽 가이드
방금 API가 401 Unauthorized를 반환했습니다. Authorization: Bearer eyJhbGciOi... 헤더는 멀쩡해 보입니다. 토큰이 만료된 걸까요? audience가 틀렸을까요? 발급자가 키를 교체했을까요? 토큰 내부에 무엇이 들어 있는지 읽지 않고서는 이 질문에 답할 수 없습니다. 그리고 JWT 토큰을 디코딩(decoding)하는 데에는 시크릿도, 라이브러리도, 네트워크 연결도 필요 없습니다. JWT는 점(.)으로 연결된 세 개의 base64url 인코딩 조각이며, 디코딩은 기계적인 절차입니다. 분할하고, base64url로 풀고, JSON.parse로 파싱하면 끝입니다.
Node.js·Python·Go·브라우저에서 JWT를 디코딩하는 실제 코드, 대부분의 팀이 혼동하는 “디코딩 대 검증”의 차이, 그리고 실제로 발목을 잡는 실패 모드를 아래에서 다룹니다. 지금 당장 토큰만 들여다보면 된다면 무료 JWT 디코더로 바로 건너뛰십시오. 이 도구는 전적으로 브라우저 안에서만 동작하므로 프로덕션 토큰이 기기를 벗어나지 않습니다.
JWT란 무엇인가? (빠른 해부학)
JSON 웹 토큰(JWT)은 RFC 7519에 정의된 컴팩트하고 URL 안전한 자격 증명입니다. 사용자와 토큰 자체에 관한 데이터인 클레임(claims)을 두 당사자 사이에서 실어 나릅니다. JWT는 점으로 연결된 세 개의 base64url 인코딩 조각, 즉 헤더(header), 페이로드(payload), 서명(signature)으로 구성됩니다.
다음은 구조를 볼 수 있도록 분리한 실제 토큰입니다:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← header
.
eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0 ← payload
.
4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0 ← signature
헤더는 토큰의 서명 방식을 설명하며, 보통 { "alg": "HS256", "typ": "JWT" } 형태입니다. 페이로드는 클레임을 담습니다. sub, exp, iat 같은 등록된 클레임과 role, tenant 같은 커스텀 클레임이 여기에 들어갑니다. 서명은 헤더와 페이로드 위에 계산된 암호학적 증거로, 수신자가 변조를 감지할 수 있게 해 줍니다. Base64url은 base64의 URL 안전 변형입니다. 10분짜리 입문이 필요하다면 Base64 입문 가이드를 참고하십시오.
JWT는 현대 인증이 존재하는 모든 곳에서 만나게 됩니다. OAuth 2.0 액세스 토큰, OpenID Connect ID 토큰, Auth0·Okta·Clerk·Supabase·Firebase가 발급하는 API 자격 증명, 서비스 메시 내부의 마이크로서비스 간 토큰 등 지난 10년간 자격 증명의 기본 포맷이었습니다.
더 나아가기 전에 반드시 체화해야 할 한 문장이 있습니다. JWT는 인코딩된 것이지 암호화된 것이 아닙니다. 토큰을 가진 사람은 누구나 모든 클레임을 읽을 수 있습니다. 서명은 출처를 증명할 뿐, 내용을 숨기지 않습니다. 이 사실 하나가 이후 모든 내용을 결정합니다. 페이로드에 무엇을 넣어도 안전한지, 왜 디코딩에는 시크릿이 필요 없는지, 왜 서버 측 서명 검증을 타협할 수 없는지가 여기서 갈립니다.
JWT 디코딩이 작동하는 방식 (암호 해독이 아닌 base64url)
JWT 디코딩은 암호학적 연산이 아닙니다. 네 단계의 기계적 절차입니다.
- 토큰을
.기준으로 정확히 세 개의 세그먼트로 분할합니다. - 첫 번째 세그먼트를 base64url 디코딩하고 JSON으로 파싱하면 헤더가 됩니다.
- 두 번째 세그먼트를 base64url 디코딩하고 JSON으로 파싱하면 페이로드가 됩니다.
- 세 번째 세그먼트(서명)는 원시 바이트로 남겨 둡니다. 검증하려면 키가 필요합니다.
이게 알고리즘의 전부입니다. 라이브러리가 필수는 아닙니다. base64와 JSON 파서가 있는 언어라면 어떤 언어든 다섯 줄로 JWT를 디코딩할 수 있습니다. 메커니즘을 직접 확인하고 싶다면 저희 Base64 인코더/디코더로 2단계와 3단계를 수동으로 수행할 수 있습니다.
base64url이란 무엇인가?
Base64url은 URL과 HTTP 헤더에서 안전하게 쓰이도록 세 군데를 바꾼 평범한 base64입니다. -가 +를 대체하고, _가 /를 대체하며, 끝에 오는 = 패딩이 제거됩니다. 이 치환을 되돌리지 않은 상태로 원시 base64url을 표준 base64 디코더에 넣으면 쓰레기 값을 얻거나 오류가 납니다. 패딩 예외 케이스는 Base64 심화 가이드에서 자세히 다룹니다.
| 표준 base64 | base64url | |
|---|---|---|
| 알파벳 | A-Z a-z 0-9 + / | A-Z a-z 0-9 - _ |
| 패딩 | 끝에 = 필요 | 제거됨 |
| URL 안전? | 아니오 | 예 |
| 예시 | PDw/Pz8+ | PDw_Pz8- |
한 가지 더 짚어 둘 점이 있습니다. 서명은 클라이언트 측에서 복호화할 수 없습니다. 디코딩은 인코딩된 바이트에서 JSON으로 가는 단방향 과정입니다. 서명 검증은 별개의 연산으로, HMAC 시크릿(HS 계열 알고리즘용) 또는 발급자의 공개 키(RS·PS·ES·EdDSA용) 중 하나가 필요합니다.
디코딩에 시크릿이 필요 없는 이유
페이로드가 암호문이 아니라 base64url + JSON이기 때문입니다. 시크릿은 토큰이 변조되지 않았음을 증명하려 할 때, 즉 서명 검사 단계에서만 등장합니다. 네트워크 경로 위에 있는 누구든, 로그 라인에 토큰이 떨어진 경우 그 로그를 볼 수 있는 누구든, 브라우저를 여는 누구든 당신이 넣은 모든 클레임을 읽을 수 있습니다. 그래서 비밀번호, API 키, 혹은 수신자가 이미 알고 있는 것 이상의 PII를 JWT 페이로드 안에 절대로 넣어서는 안 됩니다. 더 넓은 위협 모델은 보안 모범 사례 가이드를 참고하십시오.
3번의 클릭으로 JWT 디코딩 — 무료 JWT 디코더
때로는 지금 당장 답이 필요합니다. 이 토큰은 만료됐나? aud 클레임이 내가 생각하는 값인가? 헤더에 alg:none이라고 적혀 있나? 이럴 때 가장 빠른 길이 저희 온라인 JWT 디코더입니다. 새벽 2시 장애 대응 경로에 맞춰 만들어졌습니다.
- 붙여넣기: 전체 토큰을 입력 영역에 붙여넣습니다. 점으로 구분된 세 세그먼트를 모두 포함해야 합니다.
- 읽기: 디코딩된 헤더·페이로드와 상단의 상태 칩을 확인합니다. 알고리즘, 발급 시각, 만료가 표시되며,
exp가 이미 과거라면 붉은Expired배지가 함께 뜹니다. - 복사: 필요한 패널을 버그 리포트·Slack 스레드·테스트 픽스처로 복사해 붙여넣습니다.
실제 프로덕션 토큰에도 안전한 이유:
- 100% 브라우저 기반. 디코딩은 네이티브
atob와JSON.parse로 실행됩니다. 네트워크 요청은 단 한 번도 없습니다. - 로깅 없음, 추적 없음, 쿠키 없음, 가입 없음.
- 페이지가 한 번 로드되면 오프라인에서도 동작합니다.
JWT 디코더 도구는 알고리즘에 구애받지 않습니다. 디코딩에 필요한 건 base64url과 JSON뿐이기에 모든 JWS 변형을 읽을 수 있습니다. HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512, EdDSA, 그리고 alg:none까지 지원합니다. 알고리즘에 의존하는 것은 서명 검증뿐이며, 검증은 공개 웹 도구가 해서는 안 되는 일입니다. 이 이야기는 뒤에서 다시 다룹니다.
도구 결과를 교차 확인하기 위해 세그먼트를 수동으로 base64 디코딩해야 한다면, 저희 Base64 인코더/디코더에 각 세그먼트를 base64url로 넣어 보십시오.
코드로 JWT 디코딩하기 (Node.js, Python, Go, 브라우저)
대화형 디버깅이 아니라 미들웨어, 테스트, 마이그레이션 스크립트, CLI 도구처럼 코드 안에서 다뤄야 한다면 라이브러리를 찾게 됩니다. 다음은 가장 흔히 마주치는 네 가지 환경에서 JWT를 디코딩하는 최소 코드이며, 읽기 전용 경로와 검증 경로를 나란히 보여 줍니다. 모든 스니펫은 복사해 붙여넣을 수 있고, 주석에 표시된 출력을 그대로 내놓습니다.
Node.js에서 JWT 디코딩 (jsonwebtoken)
// npm install jsonwebtoken
const jwt = require('jsonwebtoken');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' +
'.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0' +
'.4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0';
// 디코딩만 수행 — 서명은 검증하지 않습니다
const decoded = jwt.decode(token, { complete: true });
console.log(decoded.header); // { alg: 'HS256', typ: 'JWT' }
console.log(decoded.payload); // { sub: 'user_123', exp: 1999999999 }
// 검증 — 프로덕션 경로
const secret = process.env.JWT_SECRET;
const verified = jwt.verify(token, secret, { algorithms: ['HS256'] });
verify에는 항상 명시적인 algorithms 허용 목록을 전달하십시오. 이를 생략하면 공격자가 공개 키를 HMAC 시크릿으로 사용해 RS256 토큰을 HS256으로 다운그레이드하는 전형적인 알고리즘 혼동 공격이 가능해집니다(과거에 여러 차례 터진 실제 사례입니다). 허용 목록이 방어선입니다.
Python에서 JWT 디코딩 (PyJWT)
# pip install PyJWT
import jwt
token = (
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
".eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0"
".4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0"
)
# 디코딩만 수행 — 인증에는 위험, 검사에는 적합
decoded = jwt.decode(token, options={"verify_signature": False})
print(decoded) # {'sub': 'user_123', 'exp': 1999999999}
# 페이로드는 건드리지 않고 헤더만 확인
header = jwt.get_unverified_header(token)
print(header) # {'alg': 'HS256', 'typ': 'JWT'}
# 검증 — 프로덕션 경로
payload = jwt.decode(
token,
key="your-hs256-secret",
algorithms=["HS256"],
audience="api.example.com",
)
PyJWT는 algorithms 목록 없이는 검증 자체를 거부합니다. Node 예제에서 경고한 것과 동일한 혼동 공격을 막아 주는 합리적인 기본값입니다.
Go에서 JWT 디코딩 (golang-jwt/jwt/v5)
// go get github.com/golang-jwt/jwt/v5
package main
import (
"fmt"
"github.com/golang-jwt/jwt/v5"
)
func main() {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
".eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0" +
".4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0"
// 디코딩만 수행
parser := jwt.NewParser()
claims := jwt.MapClaims{}
_, _, err := parser.ParseUnverified(tokenString, claims)
if err != nil {
panic(err)
}
fmt.Println(claims["sub"], claims["exp"]) // user_123 1.999999999e+09
// 검증
secret := []byte("your-hs256-secret")
token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected alg: %v", t.Header["alg"])
}
return secret, nil
})
fmt.Println(token.Valid, err)
}
keyFunc 클로저는 알고리즘 계열을 강제하는 지점입니다. 기대한 방식이 아니면 키를 반환하기 전에 거부하십시오.
브라우저에서 JWT 디코딩 (의존성 제로)
때로는 의존성을 아예 추가하고 싶지 않을 때가 있습니다. 빠른 디버그 패널, 브라우저 확장, 현재 사용자의 역할을 보여 주는 작은 UI 배지 같은 경우가 그렇습니다. 이럴 때는 네이티브 브라우저 API만으로 충분합니다.
function decodeJwt(token) {
const [h, p] = token.split('.');
const pad = (s) => s + '==='.slice((s.length + 3) % 4);
const decodeSegment = (s) => {
const b64 = pad(s).replace(/-/g, '+').replace(/_/g, '/');
const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
return JSON.parse(new TextDecoder().decode(bytes));
};
return { header: decodeSegment(h), payload: decodeSegment(p) };
}
const { header, payload } = decodeJwt(token);
console.log(header); // { alg: 'HS256', typ: 'JWT' }
console.log(payload); // { sub: 'user_123', exp: 1999999999 }
페이로드에 ASCII가 아닌 값(표시 이름의 이모지, preferred_username의 키릴 문자 등)이 들어간 토큰에서는 TextDecoder 단계가 중요합니다. atob만 쓰면 바이너리 문자열이 반환되어 멀티바이트 UTF-8에서 JSON.parse가 깨지기 때문입니다. 이것이 바로 저희 온라인 JWT 디코더가 UI만 얹은 채 당신의 브라우저 안에서 로컬로 실행하는 로직입니다.
비교 표
| 언어 | 디코딩만 | 검증 | 라이브러리 |
|---|---|---|---|
| Node.js | jwt.decode(token) | jwt.verify(token, key, { algorithms: [...] }) | jsonwebtoken |
| Python | jwt.decode(token, options={"verify_signature": False}) | jwt.decode(token, key, algorithms=[...]) | PyJWT |
| Go | parser.ParseUnverified(token, claims) | jwt.Parse(token, keyFunc) | golang-jwt/jwt/v5 |
| 브라우저 | atob + TextDecoder + JSON.parse | 백엔드에 위임 | — |
디코딩 대 검증 — 결정적 차이
JWT 디코딩은 클레임을 읽는 일이고, JWT 검증은 그 클레임이 변조되지 않았음을 증명하는 일입니다. 디코딩은 결코 신뢰하지 않으며, 검증이 바로 그 신뢰입니다. 이 구분 하나가 동작하는 인증 구현과 CVE를 가르는 경계선입니다.
| 디코딩 | 검증 | |
|---|---|---|
| 시크릿/키 필요? | 아니오 | 예 |
| 클라이언트 측 실행? | 안전함 | 절대 금지 |
| 진위 증명? | 아니오 | 예 |
| 만료 확인? | 선택 사항 | 예 |
| 용도 | 디버깅, 검사 | 인증, 인가 |
디코딩된(검증되지 않은) JWT로 인가 판단을 내려서는 절대로 안 됩니다. 미들웨어든, React 훅이든, 게이트웨이 뒤에 있다고 믿고 있는 서버리스 함수든 예외는 없습니다. 디코딩된 클레임은 토큰이 주장하는 내용을 말할 뿐이고, 검증된 클레임이야말로 발급자가 서명한 내용을 말합니다. 서명이 유효하지 않은 손수 만든 토큰을 서버에 건네는 공격자는 페이로드에 원하는 무엇이든 넣을 수 있으며, 오직 서명 검사만이 이를 거부합니다.
HMAC 계열에 관한 한 가지 중요한 디테일이 있습니다. HS256을 쓴다면 시크릿의 엔트로피가 전부입니다. 짧고 추측 가능한 시크릿은 공격자가 가로챈 토큰에 대해 오프라인으로 브루트포스당하고, 그러면 공격자가 직접 토큰을 발행해 정문으로 걸어 들어옵니다. 최소 256비트의 진짜 랜덤을 쓰십시오. 이것이 왜 중요한지에 대한 산술은 HMAC 시크릿 강도 가이드를 참고하십시오.
자주 쓰이는 JWT 클레임 레퍼런스
당신이 만나는 JWT는 모두 RFC 7519의 등록된 클레임 일부를 사용합니다. 이 짧은 목록은 외워 두는 편이 좋습니다.
| 클레임 | 이름 | 예시 | 설명 |
|---|---|---|---|
iss | Issuer | https://auth.example.com | 토큰을 발급한 주체 |
sub | Subject | user_123 | 보통 사용자 ID |
aud | Audience | api.example.com | 토큰 수신 대상 — 서버에서 반드시 일치해야 함 |
exp | Expiration | 1715003600 | Unix 초; 과거면 만료 |
iat | Issued At | 1715000000 | 토큰이 발급된 Unix 초 |
nbf | Not Before | 1715000060 | 토큰을 사용할 수 있는 가장 이른 시각 |
jti | JWT ID | d1f8… | 토큰마다 고유; 재생 공격 방지 |
kid | Key ID (헤더) | key-2025-01 | JWKS 중 어떤 키로 서명했는지 |
애플리케이션 고유 클레임이 이 곁에 자리 잡습니다. role, scope, email, tenant_id 등 당신의 IdP가 내보내는 모든 것이 여기 들어갑니다. 짧게 유지하십시오. 모든 바이트가 매 요청마다 함께 실려 다닙니다.
iat와 exp에서 사람이 읽을 수 있는 날짜를 얻으려면 저희 Unix 타임스탬프 변환기를 사용해 보십시오. 숫자를 붙여넣으면 로컬 시간대 기준 날짜가 나오고, 스큐 버그도 1초 만에 찾아낼 수 있습니다.
트러블슈팅 — 내 JWT가 왜 디코딩되지 않을까?
빈도 순으로 정리한 다섯 가지 실제 실패 모드입니다. 각 항목은 증상 → 원인 → 해결 순서로 이어집니다.
- “잘못된 JWT 형식 — 세 세그먼트가 필요합니다.” 페이로드만 복사했거나, 셸이 토큰을 여러 줄로 줄바꿈해서 첫 줄만 복사된 경우입니다. 해결: 렌더링된 터미널이 아니라 원래 응답 본문에서
xxx.yyy.zzz전체 값을 다시 복사하십시오. 긴 한 줄 값은 스크롤된 터미널보다 브라우저 devtools의 네트워크 탭에서 훨씬 잘 보존됩니다. - 세 개가 아니라 다섯 개 세그먼트. 이 경우 JWS가 아니라 JWE(암호화된 JWT)를 다루고 있습니다. 형식은
header.encryptedKey.iv.ciphertext.tag입니다. 디코더는 헤더는 읽지만 페이로드는 암호문입니다. 해결: 페이로드 디코딩에는 복호화 키가 필요합니다 — 보통 디버그 도구가 아니라 서버 측 인증 SDK가 처리합니다. - 겉보기에는 멀쩡한 토큰에서 base64url 오류. 복사 경로 어딘가(쿠키, 리다이렉트 URL, 캡처된 프록시 로그 등)에서 토큰이 URL 인코딩된 경우입니다. 문자열 안에서
%2E나%2B같은 문자를 직접 볼 수 있습니다. 해결: 먼저 URL 디코딩한 다음 JWT 디코더에 넣으십시오. - 페이로드에서 JSON 파싱 오류. 터미널이나 채팅 클라이언트가 소프트랩 개행을 끼워 넣었거나, 스크립트가 식별자 주변에 스마트 따옴표를 붙인 경우입니다. 해결: 응답의 원시 바이트를 확인(curl에
-o file.txt를 주거나 devtools의 Raw 뷰)하고 공백을 제거한 뒤 다시 붙여넣으십시오. - 디코딩은 깔끔한데 백엔드가 여전히 거부. 이건 디코딩 문제가 아니라 검증 문제입니다. 토큰 구조는 유효하지만 서버가 검사하는 무언가(서명,
aud,exp, 시계 스큐,kid조회)가 실패한 것입니다. 다음 섹션으로 넘어가십시오.
파싱 오류는 아니지만 디코더를 연 김에 같이 잡아 둘 만한 사례 두 가지가 있습니다. 하나는 헤더의 alg 값이 none인 경우로, 프로덕션에서는 적대적이라고 간주하십시오. 다른 하나는 exp 값이 과거인 경우입니다. 디코더는 그래도 클레임을 보여 줘서 디버깅이 가능하도록 해 주며(이것이 올바른 동작입니다), 저희 도구는 붉은 Expired 배지로 이를 표시합니다.
디코딩만으로 부족할 때 — 서명 검증
디코딩은 “토큰이 주장하는 바는 이렇다”에서 끝납니다. 검증은 그 주장을 신뢰 판단으로 바꾸는 단계입니다. 서명은 발급자의 개인 키 또는 공유 시크릿으로 계산된, 헤더와 페이로드를 묶어 두는 증거이며, 한 바이트라도 바꾸면 서명 검사는 실패합니다. 이 검사를 하지 않으면 엔드포인트에 POST할 수 있는 누구든 페이로드를 편집하고 서명은 완전히 건너뛴 채로 “admin” 토큰을 직접 만들어 건넬 수 있습니다.
alg:none은 절대 받아들이지 마십시오.
모든 언어와 프레임워크의 프로덕션 검증은 대략 다음과 같은 체크리스트로 정리됩니다. 누락된 항목은 버그로 간주하십시오.
algorithms: ['RS256'](또는 사용하는 알고리즘) 같은 명시적 허용 목록을 전달하십시오. 이것이 알고리즘 혼동 공격을 막습니다.aud가 서비스 식별자와 일치하고iss가 기대한 발급자 URL과 일치하는지 검증하십시오.exp를 현재 시각과 비교하되 최대 60초의 시계 스큐 허용치만 두십시오.- 키 교체 시에는 JWKS 엔드포인트에서
kid로 공개 키를 조회하십시오. 단일 키를 영구 하드코딩하지 마십시오. - 만료 시간(
exp)을 짧게(일이 아닌 분 단위) 유지하고, 가치가 높은 토큰에는 선택적으로jti차단 목록을 운영하여 효과적으로 폐기하십시오.
주류 JWT 라이브러리는 모두 이 옵션들을 단일 호출의 옵션으로 노출합니다. 검증 코드에 이들이 설정돼 있지 않다면 기본값을 그대로 두고 있다는 뜻이며, 역사적으로 그 기본값이 바로 버그의 원천이었습니다. 전체 위협 모델은 보안 모범 사례 가이드를 참고하십시오.
FAQ
시크릿 키 없이 JWT를 디코딩할 수 있나요?
네, 가능합니다. 헤더와 페이로드는 암호화된 것이 아니라 base64url로 인코딩된 것입니다 — 토큰을 가진 사람은 누구나 클레임을 읽을 수 있습니다. 시크릿이나 공개 키는 서명을 검증할 때만 필요합니다. 이는 설계에 의한 동작입니다: 페이로드는 수신자가 인가 판단을 내릴 수 있도록 읽을 수 있게 만들어져 있습니다.
프로덕션 JWT를 온라인 디코더에 붙여넣어도 안전한가요?
디코더가 브라우저 안에서 실행되고 토큰을 업로드하지 않는 경우에만 안전합니다. 저희 JWT 디코더는 네이티브 atob와 JSON.parse로 로컬에서 파싱하며, 어떤 서버에도 아무것도 전송되지 않습니다. 반대로 토큰을 API에 POST하는 원격 디버거는 자격 증명 유출로 취급해야 합니다.
JWT 디코딩과 검증의 차이는 무엇인가요?
디코딩은 단지 클레임을 읽는 것입니다. 키도 필요 없고, 아무것도 증명하지 않습니다. 검증은 발급자의 키에 대해 서명을 검사하고 토큰이 변조되지 않았음을 확인합니다. 디코딩만 되고 검증되지 않은 토큰으로는 절대 인증 판단을 내리지 마십시오.
JWT가 잘려 보이는데, 유효한 형식의 기준은 무엇인가요?
유효한 JWT는 점으로 구분된 정확히 세 개의 base64url 세그먼트 header.payload.signature로 이루어집니다. 다섯 세그먼트라면 JWS가 아니라 암호화된 JWT(JWE)를 들고 있는 것입니다. 점이 하나도 없다면 줄바꿈된 터미널 라인에서 한 세그먼트만 복사한 것입니다.
디코더가 왜 만료된 토큰도 보여 주나요?
디코더는 거부 사유를 디버깅할 수 있도록 유효성과 상관없이 클레임을 읽어 줍니다. 만료된 토큰을 거부하는 것은 오직 검증기뿐입니다. 저희 도구는 exp를 로컬 시계와 비교해 Expired 배지를 띄우므로, Unix 타임스탬프를 눈 비비며 볼 필요 없이 문제를 즉시 알아차릴 수 있습니다.
어떤 알고리즘을 디코딩할 수 있나요?
전부 가능합니다. 디코딩은 base64url과 JSON 파싱만 필요하기 때문에 알고리즘에 구애받지 않습니다. 여기에는 HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512, EdDSA, 그리고 alg:none이 포함됩니다. 알고리즘에 의존하는 것은 검증뿐입니다.
Node.js에서는 jwt-decode와 jsonwebtoken 중 무엇을 써야 하나요?
프론트엔드에서 페이로드를 읽기만 하면 될 때는(예를 들어 액세스 토큰에서 사용자명을 보여 주는 경우) jwt-decode를 쓰십시오. 백엔드에서는 jsonwebtoken을 쓰십시오. 서명 키를 보관하고 jwt.verify를 수행할 수 있는 것은 오직 백엔드뿐이기 때문입니다. 클라이언트에서는 절대 검증하지 마십시오.
결론
JWT 디코딩은 “암호학적 토큰”이라는 표현이 암시하는 것만큼 신비롭지 않습니다. 다음 다섯 가지를 붙잡고 있으면 불투명한 eyJhbGciOi… 문자열 앞에서 다시는 멈칫할 일이 없습니다.
- 디코딩은 base64url + JSON 파싱이다. 시크릿은 필요 없다.
- JWT는 점으로 연결된 세 부분, 즉 헤더·페이로드·서명으로 이루어진다.
- 디코딩은 진위를 증명하지 않는다. 서버 측에서 발급자의 키로 항상 검증하라.
alg:none을 거부하고verify에는 언제나 명시적 알고리즘 허용 목록을 전달하라.- 비밀번호·개인 키·민감한 PII를 페이로드에 절대 저장하지 말라. 토큰을 가진 사람 누구나 읽을 수 있다.
온콜 디버깅을 위해 저희 무료 JWT 디코더를 북마크해 두십시오. 토큰을 붙여넣고, 클레임을 읽고, 1초 만에 만료를 포착할 수 있으며, 토큰은 브라우저를 절대 벗어나지 않습니다.