JWT Decoder Python — Hướng dẫn giải mã JWT với PyJWT

·DevOps Engineer & Python Automation Specialist·Đánh giá bởiMaria Santos·Đã xuất bản

Sử dụng Giải mã JWT miễn phí trực tiếp trên trình duyệt — không cần cài đặt.

Dùng thử Giải mã JWT trực tuyến →

Mọi API sử dụng xác thực dựa trên token đều sẽ trả cho bạn một JWT vào một lúc nào đó, và việc tìm hiểu nội dung bên trong là một tác vụ xuất hiện liên tục trong quá trình phát triển. Một bộ giải mã JWT trong Python nhận chuỗi base64 mờ đục đó và chuyển nó thành một dict các claim dễ đọc để bạn có thể làm việc thực sự. Gói PyPI bạn cần là PyJWT — cài đặt bằng pip install PyJWT nhưng import bằng import jwt. Hướng dẫn này đi qua jwt.decode() với xác thực chữ ký đầy đủ, giải mã không cần khóa bí mật để kiểm tra nhanh, giải mã base64 thủ công không cần thư viện, xác thực khóa công khai RS256, và các lỗi phổ biến tôi đã gặp trong hệ thống xác thực sản xuất. Để kiểm tra nhanh một lần, JWT Decoder trực tuyến làm điều này ngay lập tức mà không cần viết code. Tất cả ví dụ hướng tới Python 3.10+ và PyJWT 2.x.

  • pip install PyJWT, sau đó import jwt — tên gói và tên import khác nhau, điều này khiến hầu hết mọi người bị nhầm.
  • jwt.decode(token, key, algorithms=["HS256"]) trả về dict thuần chứa các claim. Luôn truyền algorithms một cách tường minh.
  • Để kiểm tra claim mà không xác thực: jwt.decode(token, options={"verify_signature": False}, algorithms=["HS256"]).
  • Với token RSA/EC: pip install PyJWT cryptography — backend cryptography là bắt buộc cho các thuật toán bất đối xứng.
  • Giải mã thủ công (base64 + json) hoạt động không cần thư viện nhưng bỏ qua toàn bộ xác thực chữ ký và thời hạn.

Giải mã JWT là gì?

JSON Web Token gồm ba phần được mã hóa base64url, ngăn cách bằng dấu chấm: phần header (thuật toán và loại token), phần payload (các claim — ID người dùng, vai trò, thời gian hết hạn), và chữ ký. Giải mã JWT có nghĩa là trích xuất các phần header và payload, giải mã base64url, và phân tích JSON kết quả thành một dict các claim.

Header cho biết thuật toán nào được dùng để ký token và đôi khi bao gồm một kid (key ID) để tìm khóa xác thực phù hợp. Payload chứa dữ liệu thực sự: token được cấp cho ai (sub), khi nào hết hạn (exp), dịch vụ nào dùng nó (aud), cùng với các claim tùy chỉnh mà ứng dụng của bạn định nghĩa. Phần chữ ký chứng minh token chưa bị giả mạo, nhưng bạn cần khóa bí mật hoặc khóa công khai để xác thực nó. Giải mã và xác thực là hai thao tác riêng biệt. Bạn có thể giải mã payload mà không xác thực chữ ký (hữu ích khi gỡ lỗi), nhưng không bao giờ tin tưởng các claim chưa xác thực để đưa ra quyết định phân quyền.

Before · json
After · json
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
{
  "sub": "usr_8f2a",
  "role": "admin",
  "exp": 1711815600
}

jwt.decode() — Giải mã và xác thực với PyJWT

jwt.decode() là hàm chính từ thư viện PyJWT. Nó nhận chuỗi token đã mã hóa, khóa bí mật (cho thuật toán HMAC) hoặc khóa công khai (cho RSA/EC), và danh sách algorithms bắt buộc. Hàm xác thực chữ ký, kiểm tra các claim chuẩn như exp nbf, và trả về payload dưới dạng dictionary Python. Nếu có bất cứ điều gì thất bại — chữ ký sai, token hết hạn, thuật toán không đúng — nó sẽ ném ra một ngoại lệ cụ thể.

Ví dụ hoạt động tối giản

Python 3.10+
import jwt

# Khóa bí mật dùng chung giữa bên phát hành và dịch vụ này
SECRET_KEY = "k8s-webhook-signing-secret-2026"

# Mã hóa token trước (mô phỏng những gì máy chủ xác thực sẽ phát hành)
token = jwt.encode(
    {"sub": "usr_8f2a", "role": "admin", "team": "platform"},
    SECRET_KEY,
    algorithm="HS256"
)
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3Jf...

# Giải mã và xác thực token
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin', 'team': 'platform'}
print(payload["role"])
# admin

Tham số algorithms là một danh sách, không phải chuỗi đơn, và bắt buộc trong PyJWT 2.x. Đây là một tính năng bảo mật: nếu không có nó, kẻ tấn công có thể tạo token với alg: none trong header và bỏ qua hoàn toàn việc xác thực. Luôn chỉ định chính xác các thuật toán mà ứng dụng của bạn chấp nhận. Nếu bạn chỉ phát hành token HS256, danh sách nên là ["HS256"] — không phải ["HS256", "RS256", "none"]. Giữ danh sách ngắn gọn giúp giảm bề mặt tấn công.

Một điều khiến tôi bối rối lúc đầu: PyJWT 2.x đã thay đổi jwt.encode() để trả về chuỗi thay vì bytes. Nếu bạn đọc các câu trả lời Stack Overflow cũ gọi .decode("utf-8") trên token đã mã hóa, code đó từ thời PyJWT 1.x và sẽ ném ra AttributeError trên phiên bản 2.x. Token đã là chuỗi — chỉ cần dùng trực tiếp.

Vòng đời đầy đủ với thời hạn

Python 3.10+ — encode then decode with exp
import jwt
from datetime import datetime, timedelta, timezone

SECRET_KEY = "webhook-processor-secret"

# Tạo token hết hạn sau 1 giờ
payload = {
    "sub": "svc_payment_processor",
    "iss": "auth.internal.example.com",
    "aud": "https://api.example.com",
    "exp": datetime.now(timezone.utc) + timedelta(hours=1),
    "permissions": ["orders:read", "refunds:create"],
}

token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")

# Sau đó, khi token đến trong header của request:
try:
    decoded = jwt.decode(
        token,
        SECRET_KEY,
        algorithms=["HS256"],
        audience="https://api.example.com",
        issuer="auth.internal.example.com",
    )
    print(f"Service: {decoded['sub']}")
    print(f"Permissions: {decoded['permissions']}")
except jwt.ExpiredSignatureError:
    print("Token expired — request re-authentication")
except jwt.InvalidAudienceError:
    print("Token not intended for this API")
except jwt.InvalidIssuerError:
    print("Token issued by unknown authority")
Lưu ý:PyJWT tự động chuyển đổi đối tượng datetime thành Unix timestamp trong quá trình mã hóa. Khi giải mã, các claim exp, iat nbf trả về dưới dạng số nguyên, không phải đối tượng datetime. Bạn cần tự chuyển đổi bằng datetime.fromtimestamp(payload["exp"], tz=timezone.utc).

Giải mã JWT không xác thực chữ ký

Đôi khi bạn cần đọc các claim trước khi có thể xác thực token. Một tình huống phổ biến: header của token chứa trường kid (key ID), và bạn cần lấy khóa công khai phù hợp từ endpoint JWKS trước khi xác thực. PyJWT hỗ trợ điều này với tùy chọn verify_signature: False. Bạn vẫn truyền danh sách algorithms, nhưng đối số key sẽ bị bỏ qua.

Python 3.10+ — unverified decode
import jwt

token = (
    "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZy0xNzI2In0"
    ".eyJzdWIiOiJ1c3JfM2M3ZiIsInNjb3BlIjoicmVhZDpvcmRlcnMiLCJpc3MiOiJhdXRoLmV4YW1wbGUuY29tIn0"
    ".signature_placeholder"
)

# Bước 1: Đọc claim không xác thực để lấy thông tin định tuyến
unverified = jwt.decode(
    token,
    options={"verify_signature": False},
    algorithms=["RS256"]
)
print(unverified)
# {'sub': 'usr_3c7f', 'scope': 'read:orders', 'iss': 'auth.example.com'}

# Bước 2: Đọc header để tìm khóa nào cần dùng
header = jwt.get_unverified_header(token)
print(header)
# {'alg': 'RS256', 'typ': 'JWT', 'kid': 'sig-1726'}
# Giờ dùng header['kid'] để lấy khóa công khai đúng từ endpoint JWKS
Cảnh báo:Token chưa xác thực không đáng tin cậy. Chỉ dùng mẫu này cho các quyết định định tuyến (cần lấy khóa nào, cần tra cứu tenant nào). Không bao giờ đưa ra quyết định phân quyền dựa trên claim chưa xác thực. Kẻ tấn công có thể đặt bất cứ thứ gì vào payload.

Có một sự khác biệt tinh tế ở đây. jwt.get_unverified_header() chỉ đọc header — phần đầu tiên. Lời gọi jwt.decode() với verify_signature: False đọc payload (phần thứ hai). Kết hợp cả hai, bạn có thể trích xuất mọi thứ từ một token mà không cần khóa. PyJWT vẫn kiểm tra rằng token có cấu trúc đúng (ba phần cách nhau bằng dấu chấm, base64 hợp lệ, JSON hợp lệ) ngay cả khi tắt xác thực chữ ký. Nếu token bị dị dạng về mặt cấu trúc, nó sẽ ném DecodeError bất kể tùy chọn bạn truyền vào.

Tham chiếu tham số jwt.decode()

Chữ ký đầy đủ là jwt.decode(jwt, key, algorithms, options, audience, issuer, leeway, require). Tất cả tham số sau algorithms chỉ được truyền theo tên (keyword-only).

Tham số
Kiểu
Mặc định
Mô tả
jwt
str | bytes
(bắt buộc)
Chuỗi JWT đã mã hóa cần giải mã
key
str | bytes | dict
(bắt buộc)
Khóa bí mật (HMAC) hoặc khóa công khai (RSA/EC) để xác thực
algorithms
list[str]
(bắt buộc)
Các thuật toán được phép — ví dụ ["HS256"], ["RS256"]. Không được bỏ qua.
options
dict
{}
Ghi đè các cờ xác thực: verify_signature, verify_exp, verify_aud, v.v.
audience
str | list[str]
None
Giá trị aud mong đợi — ném InvalidAudienceError nếu không khớp
issuer
str
None
Giá trị iss mong đợi — ném InvalidIssuerError nếu không khớp
leeway
timedelta | int
0
Số giây dung sai lệch đồng hồ cho kiểm tra exp và nbf
require
list[str]
[]
Các claim bắt buộc phải có — ném MissingRequiredClaimError nếu thiếu

Dict options cho phép kiểm soát chi tiết những xác thực nào PyJWT thực hiện. Các khóa tương ứng với từng kiểm tra riêng lẻ: verify_signature, verify_exp, verify_nbf, verify_iss, verify_aud, và verify_iat. Tất cả mặc định là True trừ khi bạn đặt tường minh thành False. Trong môi trường sản xuất, để tất cả ở giá trị mặc định. Lần duy nhất tôi tắt từng kiểm tra riêng lẻ là khi phát triển, làm việc với token thử nghiệm cũ và cần tạm thời bỏ qua kiểm tra thời hạn.

Python 3.10+ — using options and require
import jwt

# Yêu cầu các claim cụ thể phải có — ném MissingRequiredClaimError nếu thiếu
payload = jwt.decode(
    token,
    SECRET_KEY,
    algorithms=["HS256"],
    options={"require": ["exp", "iss", "sub"]},
    issuer="auth.internal.example.com",
)

# Chỉ trong phát triển: bỏ qua thời hạn để test với token cũ
dev_payload = jwt.decode(
    token,
    SECRET_KEY,
    algorithms=["HS256"],
    options={"verify_exp": False},  # KHÔNG dùng trong môi trường sản xuất
)

Giải mã JWT thủ công với base64 và json

Bạn có thể giải mã payload JWT chỉ bằng thư viện chuẩn Python — không cần pip install gì cả. Điều này thực sự hữu ích trong một số tình huống: các script gỡ lỗi nơi thêm một phụ thuộc là thừa, môi trường CI hạn chế chỉ có thư viện chuẩn, các hàm AWS Lambda nơi bạn muốn giảm thiểu thời gian khởi động lạnh, hoặc đơn giản là để hiểu JWT thực sự là gì bên dưới. Quá trình rất đơn giản: tách theo dấu chấm, lấy phần cần thiết, thêm padding base64, giải mã, và phân tích JSON.

Cả hai module base64 json đều có trong thư viện chuẩn Python, vì vậy cách tiếp cận này hoạt động trên mọi cài đặt Python từ 3.6 trở đi. Các hàm dưới đây xử lý cả header (phần đầu) và payload (phần thứ hai) riêng biệt:

Python 3.10+ — manual JWT decode without any library
import base64
import json

def decode_jwt_payload(token: str) -> dict:
    """Decode the JWT payload without signature verification.
    Works with any JWT — HS256, RS256, ES256, etc.
    """
    parts = token.split(".")
    if len(parts) != 3:
        raise ValueError(f"Expected 3 JWT segments, got {len(parts)}")

    payload_b64 = parts[1]
    # base64url uses - and _ instead of + and /
    # Python's urlsafe_b64decode handles this, but needs padding
    payload_b64 += "=" * (-len(payload_b64) % 4)
    payload_bytes = base64.urlsafe_b64decode(payload_b64)
    return json.loads(payload_bytes)


def decode_jwt_header(token: str) -> dict:
    """Decode the JWT header (algorithm, key ID, type)."""
    header_b64 = token.split(".")[0]
    header_b64 += "=" * (-len(header_b64) % 4)
    return json.loads(base64.urlsafe_b64decode(header_b64))


# Example usage
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"

header = decode_jwt_header(token)
print(f"Algorithm: {header['alg']}")
# Algorithm: HS256

claims = decode_jwt_payload(token)
print(f"Subject: {claims['sub']}")
print(f"Role: {claims['role']}")
# Subject: usr_8f2a
# Role: admin

Mẹo padding (+= "=" * (-len(s) % 4)) là phần mà mọi người hay quên. JWT base64url bỏ qua các ký tự = đệm ở cuối, nhưng hàm urlsafe_b64decode của Python yêu cầu chúng. Không có bước sửa padding, bạn sẽ gặp lỗi binascii.Error: Incorrect padding.

Lưu ý:Giải mã thủ công không xác thực chữ ký. Bất kỳ ai cũng có thể thay đổi payload của JWT và mã hóa lại. Chỉ dùng cách này để kiểm tra và gỡ lỗi, không bao giờ cho logic phân quyền. Với bất cứ điều gì quan trọng, hãy dùng jwt.decode() với khóa thực.

Giải mã JWT từ phản hồi API và tệp token

Hai tình huống thực tế phổ biến nhất: trích xuất JWT từ phản hồi HTTP (endpoint token OAuth, API đăng nhập), và đọc token từ tệp (thông tin xác thực service account, secret được mount trong Kubernetes, token được lưu vào bộ nhớ đệm trên đĩa). Cả hai đều cần xử lý lỗi đúng cách. Yêu cầu mạng có thể thất bại. Tệp có thể mất. Token có thể hết hạn giữa lúc được lưu vào bộ nhớ đệm và lúc được đọc.

Các ví dụ dưới đây dùng httpx cho các cuộc gọi HTTP (thay thế bằng requests nếu bạn thích, cách viết hoàn toàn giống nhau) và pathlib.Path cho các thao tác với tệp. Mỗi ví dụ bắt các ngoại lệ PyJWT cụ thể thay vì một except Exception chung chung, để bạn có thể phản ứng phù hợp với từng loại lỗi: xác thực lại khi hết hạn, cảnh báo khi chữ ký thất bại, thử lại khi hết thời gian chờ mạng.

Giải mã JWT từ phản hồi API

Python 3.10+ — decode JWT from OAuth token endpoint
import jwt
import httpx  # or requests

TOKEN_ENDPOINT = "https://auth.example.com/oauth/token"
SECRET_KEY = "shared-webhook-signing-key"

def get_and_decode_token() -> dict:
    """Fetch an access token from the auth server and decode it."""
    try:
        response = httpx.post(
            TOKEN_ENDPOINT,
            data={
                "grant_type": "client_credentials",
                "client_id": "svc_order_processor",
                "client_secret": "cs_9f3a7b2e",
            },
            timeout=10.0,
        )
        response.raise_for_status()
    except httpx.HTTPError as exc:
        raise RuntimeError(f"Token request failed: {exc}") from exc

    token_data = response.json()
    access_token = token_data["access_token"]

    try:
        payload = jwt.decode(
            access_token,
            SECRET_KEY,
            algorithms=["HS256"],
            audience="https://api.example.com",
        )
        return payload
    except jwt.InvalidTokenError as exc:
        raise RuntimeError(f"Invalid token from auth server: {exc}") from exc


claims = get_and_decode_token()
print(f"Service: {claims['sub']}, Scopes: {claims.get('scope', 'none')}")

Giải mã JWT từ tệp

Python 3.10+ — read and decode a cached token file
import jwt
from pathlib import Path
from datetime import datetime, timezone

TOKEN_PATH = Path("/var/run/secrets/service-account-token")
PUBLIC_KEY_PATH = Path("/etc/ssl/auth/public_key.pem")

def decode_token_from_file() -> dict:
    """Read a JWT from a file, verify with a PEM public key."""
    try:
        token = TOKEN_PATH.read_text().strip()
        public_key = PUBLIC_KEY_PATH.read_text()
    except FileNotFoundError as exc:
        raise RuntimeError(f"Missing file: {exc.filename}") from exc

    try:
        payload = jwt.decode(
            token,
            public_key,
            algorithms=["RS256"],
            audience="https://internal-api.example.com",
        )
    except jwt.ExpiredSignatureError:
        exp_time = jwt.decode(
            token,
            options={"verify_signature": False},
            algorithms=["RS256"],
        ).get("exp", 0)
        expired_at = datetime.fromtimestamp(exp_time, tz=timezone.utc)
        raise RuntimeError(f"Token expired at {expired_at.isoformat()}")
    except jwt.InvalidTokenError as exc:
        raise RuntimeError(f"Token verification failed: {exc}") from exc

    return payload


claims = decode_token_from_file()
print(f"Subject: {claims['sub']}, Issuer: {claims['iss']}")

Giải mã JWT từ dòng lệnh

Đôi khi bạn chỉ cần xem nhanh một token từ terminal mà không cần viết script. Có thể bạn đang gỡ lỗi luồng OAuth và muốn xem nội dung trong header Authorization, hoặc bạn lấy token từ DevTools của trình duyệt và muốn kiểm tra thời hạn. Cờ -c của Python làm cho việc này chỉ cần một dòng lệnh. Nhập token vào và nhận các claim dưới dạng JSON có định dạng. Không cần tệp script, không cần môi trường ảo.

Bash
# Decode JWT payload from clipboard or variable (no verification)
echo "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiJ9.sig" \
  | python3 -c "
import sys, base64, json
token = sys.stdin.read().strip()
payload = token.split('.')[1]
payload += '=' * (-len(payload) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))
"
# {
#   "sub": "usr_8f2a",
#   "role": "admin"
# }
Bash
# Decode JWT header to check algorithm and key ID
echo "eyJhbGciOiJSUzI1NiIsImtpZCI6InNpZy0xNzI2In0.payload.sig" \
  | python3 -c "
import sys, base64, json
token = sys.stdin.read().strip()
header = token.split('.')[0]
header += '=' * (-len(header) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(header)), indent=2))
"
# {
#   "alg": "RS256",
#   "kid": "sig-1726"
# }
Bash
# If PyJWT is installed, verify and decode in one step
python3 -c "
import jwt, sys, json
token = sys.argv[1]
payload = jwt.decode(token, options={'verify_signature': False}, algorithms=['HS256'])
print(json.dumps(payload, indent=2))
" "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSJ9.sig"

Để có phương án trực quan mà không cần thiết lập terminal, hãy dán token của bạn vào JWT Decoder của ToolDeck và xem header, payload, và trạng thái xác thực chữ ký ngay lập tức.

python-jose và các thư viện thay thế

python-jose là thư viện JWT thay thế hỗ trợ JWS, JWE (token được mã hóa), và JWK một cách tự nhiên. Nếu ứng dụng của bạn cần xử lý JWE (token được mã hóa) — nơi bản thân payload được mã hóa, không chỉ được ký — python-jose là lựa chọn đúng vì PyJWT hoàn toàn không hỗ trợ JWE. Thư viện cũng có tính năng xử lý tập khóa JWKS tích hợp sẵn, giúp đơn giản hóa việc tích hợp với các nhà cung cấp danh tính như Auth0, Okta, hoặc Keycloak vốn cung cấp các tập khóa xoay vòng. Giao diện giải mã gần như giống hệt PyJWT, vì vậy việc chuyển đổi giữa chúng cần thay đổi code tối thiểu:

Python 3.10+ — python-jose
# pip install python-jose[cryptography]
from jose import jwt as jose_jwt

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInNjb3BlIjoib3JkZXJzOnJlYWQifQ.signature"

# Verified decode — same pattern as PyJWT
payload = jose_jwt.decode(
    token,
    "signing-secret-key",
    algorithms=["HS256"],
    audience="https://api.example.com",
)
print(payload)
# {'sub': 'usr_8f2a', 'scope': 'orders:read'}

# Unverified decode
claims = jose_jwt.get_unverified_claims(token)
header = jose_jwt.get_unverified_header(token)
print(f"Algorithm: {header['alg']}, Subject: {claims['sub']}")

Khuyến nghị của tôi: bắt đầu với PyJWT. Nó đáp ứng 95% trường hợp sử dụng JWT, có cộng đồng lớn nhất, và API rõ ràng. Chuyển sang python-jose nếu bạn cần hỗ trợ JWE hoặc thích cách xử lý JWKS của nó. Lựa chọn thứ ba đáng đề cập là Authlib, vốn gộp JWT vào một framework OAuth/OIDC lớn hơn nhiều. Nếu bạn đã dùng Authlib cho các luồng OAuth client, module authlib.jose.jwt giúp bạn không cần thêm một phụ thuộc JWT thứ hai. Nếu không, đây là một phụ thuộc nặng chỉ để giải mã token.

Đầu ra terminal với tô sáng cú pháp

Đọc claim JWT thô trong terminal là ổn cho các kiểm tra nhanh, nhưng khi bạn thường xuyên gỡ lỗi các payload token (tôi làm điều này hàng ngày khi xây dựng một cổng xác thực nội bộ), đầu ra có màu sắc tạo ra sự khác biệt thực sự. Giá trị chuỗi, số, boolean và null đều hiển thị với màu sắc riêng biệt, nghĩa là bạn có thể phát hiện một quyền hạn bị thiếu hoặc timestamp hết hạn sai chỉ nhìn qua mà không cần đọc từng ký tự.

Thư viện rich (pip install rich) có hàm print_json nhận chuỗi JSON hoặc dict Python và in ra với tô sáng cú pháp đầy đủ trong terminal. Kết hợp với PyJWT cho quy trình kiểm tra JWT chỉ cần hai dòng:

Python 3.10+ — rich terminal output
# pip install rich PyJWT
import jwt
from rich import print_json

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsInBlcm1pc3Npb25zIjpbIm9yZGVyczpyZWFkIiwicmVmdW5kczpjcmVhdGUiXSwiZXhwIjoxNzExODE1NjAwfQ.sig"

payload = jwt.decode(
    token,
    options={"verify_signature": False},
    algorithms=["HS256"]
)

# Colorized, indented JSON output in the terminal
print_json(data=payload)
# {
#   "sub": "usr_8f2a",           ← strings in green
#   "role": "admin",
#   "permissions": [
#     "orders:read",
#     "refunds:create"
#   ],
#   "exp": 1711815600            ← numbers in cyan
# }
Lưu ý:Đầu ra của rich chứa mã escape ANSI. Không ghi vào tệp hoặc trả về từ các endpoint API — nó chỉ dành cho hiển thị trong terminal. Dùng json.dumps() khi bạn cần đầu ra văn bản thuần.

Xử lý lô token lớn

Bản thân token JWT rất nhỏ (thường dưới 2 KB mỗi cái), nhưng có những tình huống bạn xử lý chúng theo lô. Phân tích nhật ký kiểm tra sau sự cố bảo mật. Script di chuyển phiên khi chuyển nhà cung cấp xác thực. Xác thực lô tuân thủ nơi bạn cần chứng minh mọi token được phát hành trong 90 ngày qua được ký bằng khóa đúng. Nếu bạn có hàng chục nghìn token trong tệp nhật ký NDJSON, xử lý từng dòng giúp tránh tải toàn bộ tệp vào bộ nhớ và cho phép báo cáo kết quả tăng dần.

Xác thực lô token từ nhật ký kiểm tra

Python 3.10+ — streaming token validation
import jwt
import json
from pathlib import Path

SECRET_KEY = "audit-log-signing-key"

def validate_token_log(log_path: str) -> dict:
    """Process an NDJSON file where each line has a 'token' field.
    Returns counts of valid, expired, and invalid tokens.
    """
    stats = {"valid": 0, "expired": 0, "invalid": 0}

    with open(log_path) as fh:
        for line_num, line in enumerate(fh, 1):
            line = line.strip()
            if not line:
                continue

            try:
                record = json.loads(line)
                token = record["token"]
            except (json.JSONDecodeError, KeyError):
                stats["invalid"] += 1
                continue

            try:
                jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
                stats["valid"] += 1
            except jwt.ExpiredSignatureError:
                stats["expired"] += 1
            except jwt.InvalidTokenError:
                stats["invalid"] += 1

    return stats


result = validate_token_log("auth-events-2026-03.ndjson")
print(f"Valid: {result['valid']}, Expired: {result['expired']}, Invalid: {result['invalid']}")
# Valid: 14832, Expired: 291, Invalid: 17

Trích xuất claim từ tệp xuất token NDJSON

Python 3.10+ — extract and transform claims from token log
import base64
import json
from datetime import datetime, timezone

def extract_claims_stream(input_path: str, output_path: str):
    """Read tokens line by line, decode payloads, write flattened claims."""
    with open(input_path) as infile, open(output_path, "w") as outfile:
        for line in infile:
            line = line.strip()
            if not line:
                continue

            record = json.loads(line)
            token = record.get("access_token", "")
            parts = token.split(".")
            if len(parts) != 3:
                continue

            payload_b64 = parts[1] + "=" * (-len(parts[1]) % 4)
            claims = json.loads(base64.urlsafe_b64decode(payload_b64))

            # Flatten into an audit-friendly record
            flat = {
                "timestamp": record.get("timestamp"),
                "subject": claims.get("sub"),
                "issuer": claims.get("iss"),
                "expired_at": datetime.fromtimestamp(
                    claims.get("exp", 0), tz=timezone.utc
                ).isoformat(),
            }
            outfile.write(json.dumps(flat) + "\n")

extract_claims_stream("token-audit.ndjson", "claims-extract.ndjson")
Lưu ý:Với các tệp dưới vài trăm MB, đọc từng dòng là đủ hiệu quả. Nếu bạn gặp giới hạn hiệu suất với các tệp dump token rất lớn, hãy cân nhắc dùng multiprocessing.Pool để phân phối quá trình xác thực qua nhiều lõi, vì mỗi token độc lập với nhau.

Các lỗi phổ biến

Bốn lỗi này xuất hiện lặp đi lặp lại trong các buổi review code và câu hỏi trên Stack Overflow. Mỗi lỗi dễ mắc phải, và thông báo lỗi PyJWT đưa ra không phải lúc nào cũng chỉ trực tiếp vào nguyên nhân. Tôi đã thấy chỉ riêng vấn đề đặt tên gói làm mất hàng giờ debug khi ai đó cài nhầm thư viện và nhận được một API hoàn toàn không mong đợi.

Nhầm lẫn tên gói PyJWT và python-jwt

Vấn đề: Chạy pip install jwt hoặc pip install python-jwt sẽ cài một gói hoàn toàn khác. import jwt sau đó thất bại hoặc cho bạn một API bạn không nhận ra.

Cách sửa: Luôn cài bằng pip install PyJWT. Lệnh import là import jwt. Kiểm tra bằng pip show PyJWT để xác nhận đúng gói.

Before · Python
After · Python
pip install jwt
# or
pip install python-jwt

import jwt  # wrong package — different API entirely
pip install PyJWT

import jwt  # correct — this is PyJWT
print(jwt.__version__)  # 2.x
Bỏ qua tham số algorithms

Vấn đề: Trong PyJWT 1.x, algorithms là tùy chọn và mặc định cho phép mọi thuật toán. Điều này tạo ra lỗ hổng bảo mật nơi kẻ tấn công có thể đặt alg: none. PyJWT 2.x bây giờ ném DecodeError nếu thiếu algorithms.

Cách sửa: Luôn truyền algorithms dưới dạng danh sách tường minh. Chỉ dùng các thuật toán mà ứng dụng của bạn thực sự phát hành token.

Before · Python
After · Python
# PyJWT 2.x — this raises DecodeError
payload = jwt.decode(token, SECRET_KEY)
# DecodeError: algorithms must be specified
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
Dùng jwt.decode() với kiểu khóa sai cho RS256

Vấn đề: Truyền chuỗi bí mật vào jwt.decode() với algorithms=["RS256"] sẽ ném InvalidSignatureError. RS256 yêu cầu khóa công khai được mã hóa PEM, không phải chuỗi bí mật dùng chung.

Cách sửa: Tải khóa công khai PEM từ tệp hoặc biến môi trường. Cài gói cryptography: pip install PyJWT cryptography.

Before · Python
After · Python
# This fails — RS256 needs a public key, not a string secret
payload = jwt.decode(token, "my-secret", algorithms=["RS256"])
# InvalidSignatureError
public_key = open("public_key.pem").read()
payload = jwt.decode(token, public_key, algorithms=["RS256"])
Quên padding base64 khi giải mã thủ công

Vấn đề: JWT base64url bỏ các ký tự = đệm ở cuối. base64.urlsafe_b64decode của Python ném binascii.Error: Incorrect padding nếu bạn truyền phần thô mà không sửa padding.

Cách sửa: Thêm padding trước khi giải mã: segment += '=' * (-len(segment) % 4). Công thức này luôn tạo ra đúng số ký tự padding (0, 1, 2, hoặc 3).

Before · Python
After · Python
import base64
payload_b64 = token.split(".")[1]
data = base64.urlsafe_b64decode(payload_b64)
# binascii.Error: Incorrect padding
import base64
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4)
data = base64.urlsafe_b64decode(payload_b64)  # works

PyJWT so với các thư viện thay thế — So sánh nhanh

Phương thức
Xác thực chữ ký
Kiểm tra claim
Kiểu tùy chỉnh
Cần cài đặt
PyJWT jwt.decode()
✓ (exp, aud, iss, nbf)
N/A (trả về dict)
pip install PyJWT
PyJWT giải mã chưa xác thực
N/A
pip install PyJWT
Giải mã base64 thủ công
N/A
Không (thư viện chuẩn)
python-jose jwt.decode()
N/A
pip install python-jose
Authlib jwt.decode()
N/A
pip install Authlib
PyJWT + cryptography
✓ (RSA/EC)
N/A
pip install PyJWT cryptography

PyJWT là điểm khởi đầu đúng đắn cho hầu hết ứng dụng Python. Nó đáp ứng HMAC và (với backend cryptography) xác thực chữ ký RSA và EC. Nếu bạn cần JWE (token được mã hóa), hãy chuyển sang python-jose hoặc Authlib. Giải mã base64 thủ công dùng được cho gỡ lỗi nhưng không cung cấp bất kỳ đảm bảo an toàn nào.

Đây là khi tôi chọn từng tùy chọn: PyJWT cho mọi web service chuẩn làm xác thực HS256 hoặc RS256. python-jose khi kiến trúc bao gồm token được mã hóa hoặc xoay vòng JWKS. Base64 thủ công cho kiểm tra nhanh trong môi trường không có pip (container CI, máy chủ sản xuất bị hạn chế, Lambda khởi động lạnh nơi bạn muốn giảm thiểu phụ thuộc). Authlib khi dự án đã dùng nó cho các luồng OAuth client và thêm thư viện JWT khác sẽ là thừa.

Để có phương án không cần code, dán bất kỳ token nào vào JWT Decoder để xem header và payload đã giải mã với phản hồi xác thực claim.

Câu hỏi thường gặp

Làm thế nào để giải mã JWT trong Python mà không xác thực chữ ký?

Truyền options={"verify_signature": False} và algorithms=["HS256"] vào jwt.decode(). Hàm này trả về dict payload mà không kiểm tra chữ ký. Chỉ dùng cách này để kiểm tra — đọc các claim trước khi lấy khóa công khai phù hợp, hoặc gỡ lỗi trong môi trường phát triển. Không bao giờ bỏ qua xác thực đối với các token kiểm soát quyền truy cập.

Python
import jwt

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

payload = jwt.decode(
    token,
    options={"verify_signature": False},
    algorithms=["HS256"]
)
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin'}

Sự khác biệt giữa PyJWT và python-jwt là gì?

PyJWT (pip install PyJWT, import jwt) là thư viện JWT phổ biến nhất cho Python với hơn 80 triệu lượt tải xuống mỗi tháng. python-jwt (pip install python_jwt) là một thư viện riêng biệt, ít được sử dụng hơn nhiều với bề mặt API khác. Nếu bạn thấy import jwt trong code của ai đó, họ đang dùng PyJWT. Sự nhầm lẫn về tên gọi xuất phát từ tên gói PyPI (PyJWT) khác với tên import (jwt). Hãy dùng PyJWT trừ khi bạn có lý do cụ thể để không làm vậy.

Làm thế nào để giải mã JWT với RS256 trong Python?

Cài đặt cả PyJWT và backend cryptography: pip install PyJWT cryptography. Sau đó truyền khóa công khai được mã hóa PEM làm đối số key và algorithms=["RS256"]. PyJWT ủy quyền việc xác thực chữ ký RSA cho thư viện cryptography. Nếu không cài gói cryptography, PyJWT sẽ báo lỗi khi bạn cố dùng thuật toán RSA hoặc EC.

Python
import jwt

public_key = open("public_key.pem").read()

payload = jwt.decode(
    token,
    public_key,
    algorithms=["RS256"],
    audience="https://api.example.com"
)

Tại sao PyJWT ném ra ExpiredSignatureError?

PyJWT kiểm tra claim exp (thời hạn) theo mặc định. Nếu thời gian UTC hiện tại đã qua mốc exp, nó sẽ ném jwt.ExpiredSignatureError. Bạn có thể thêm dung sai lệch đồng hồ bằng tham số leeway: jwt.decode(token, key, algorithms=["HS256"], leeway=timedelta(seconds=30)). Điều này cho phép khoảng trễ 30 giây. Để tắt hoàn toàn kiểm tra thời hạn (không khuyến nghị cho môi trường sản xuất), truyền options={"verify_exp": False}.

Python
import jwt
from datetime import timedelta

try:
    payload = jwt.decode(token, secret, algorithms=["HS256"], leeway=timedelta(seconds=30))
except jwt.ExpiredSignatureError:
    print("Token has expired — prompt re-authentication")

Có thể đọc JWT claim mà không cần thư viện nào trong Python không?

Có. Tách token theo dấu chấm, lấy phần thứ hai (payload), thêm ký tự = để độ dài là bội số của 4, sau đó giải mã base64url và phân tích JSON. Cách này cho bạn dict các claim nhưng không xác thực chữ ký. Hữu ích trong các môi trường hạn chế không thể cài PyJWT, hoặc cho các script gỡ lỗi nhanh.

Python
import base64, json

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSJ9.signature"
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4)  # fix padding
claims = json.loads(base64.urlsafe_b64decode(payload_b64))
print(claims)  # {'sub': 'usr_8f2a'}

Làm thế nào để xác thực claim audience với PyJWT?

Truyền tham số audience vào jwt.decode(): jwt.decode(token, key, algorithms=["HS256"], audience="https://api.example.com"). PyJWT so sánh claim aud trong token với giá trị bạn cung cấp. Nếu token không có claim aud, hoặc giá trị không khớp, nó sẽ ném jwt.InvalidAudienceError. Bạn cũng có thể truyền danh sách các audience được chấp nhận nếu dịch vụ của bạn nhận token dành cho nhiều API.

Python
import jwt

payload = jwt.decode(
    token,
    secret,
    algorithms=["HS256"],
    audience=["https://api.example.com", "https://admin.example.com"]
)

Công cụ liên quan

DV
Dmitri VolkovDevOps Engineer & Python Automation Specialist

Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.

MS
Maria SantosNgười đánh giá kỹ thuật

Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.