JWT Decoder Python — Hướng dẫn giải mã JWT với PyJWT
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.
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 và 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
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"])
# adminTham 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
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")datetime thành Unix timestamp trong quá trình mã hóa. Khi giải mã, các claim exp, iat và 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.
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 JWKSCó 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).
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.
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 và 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:
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: adminMẹ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.
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
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
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.
# 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"
# }# 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"
# }# 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:
# 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:
# 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
# }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
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: 17Trích xuất claim từ tệp xuất token NDJSON
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")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.
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.
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
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.
# 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"])
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.
# 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"])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).
import base64
payload_b64 = token.split(".")[1]
data = base64.urlsafe_b64decode(payload_b64)
# binascii.Error: Incorrect paddingimport base64
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4)
data = base64.urlsafe_b64decode(payload_b64) # worksPyJWT so với các thư viện thay thế — So sánh nhanh
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.
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.
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}.
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.
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.
import jwt
payload = jwt.decode(
token,
secret,
algorithms=["HS256"],
audience=["https://api.example.com", "https://admin.example.com"]
)Công cụ liên quan
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.
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.