Secure JWT Implementation for APIs — Complete Guide
In this tutorial, you will learn about Secure JWT Implementation for APIs. We cover key concepts, practical examples, and best practices to help you master this topic.
JSON Web Tokens (JWT) are a compact, URL-safe way to represent claims between parties. A JWT is signed, not encrypted, meaning anyone can read its contents but only the holder of the private key can forge it.
What You'll Learn
You'll learn secure JWT implementation including proper signing algorithms, claim validation, token storage, and common attacks to avoid.
Why It Matters
Improper JWT implementation leads to token forgery, algorithm confusion attacks, and sensitive data exposure. JWT vulnerabilities are among OWASP's top API risks.
Real-World Use
A payment processing API uses signed JWTs to authorize transactions. Each token contains the user ID, merchant ID, and amount. The signature ensures no one can tamper with these values.
flowchart LR
A[User Login] --> B[Server Creates JWT]
B --> C{Header: alg & typ}
B --> D{Payload: claims & exp}
B --> E{Signature: verify integrity}
C --> F[Encode JWT]
D --> F
E --> F
F --> G[Client Stores Token]
G --> H[API Request with JWT]
H --> I[Server Verifies Signature]
I -->|Valid| J[Process Request]
I -->|Invalid| K[401 Unauthorized]
Teacher's Mindset
A JWT is like a stamped passport. The passport's information is visible to anyone who reads it, but the government's stamp (signature) proves it's authentic and hasn't been altered.
Implementing Secure JWT
import jwt
from datetime import datetime, timedelta
SECRET_KEY = "your-256-bit-secret"
ALGORITHM = "HS256"
def create_access_token(user_id: str) -> str:
payload = {
"sub": user_id,
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(minutes=15),
"type": "access",
"scope": ["read", "write"]
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
def verify_token(token: str) -> dict:
try:
payload = jwt.decode(
token,
SECRET_KEY,
algorithms=[ALGORITHM],
options={"require": ["exp", "iat"]}
)
return payload
except jwt.ExpiredSignatureError:
raise ValueError("Token expired")
except jwt.InvalidTokenError:
raise ValueError("Invalid token")
# Secure validation with audience claim
def create_token(user_id, audience="api.example.com"):
payload = {
"sub": user_id,
"aud": audience,
"iss": "auth.example.com",
"exp": datetime.utcnow() + timedelta(hours=1),
"iat": datetime.utcnow(),
"jti": secrets.token_hex(16)
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
def validate_token(token, expected_audience):
try:
payload = jwt.decode(
token,
SECRET_KEY,
algorithms=[ALGORITHM],
audience=expected_audience,
issuer="auth.example.com"
)
return payload
except jwt.InvalidAudienceError:
raise ValueError("Invalid audience")
except jwt.InvalidIssuerError:
raise ValueError("Invalid issuer")
# Preventing algorithm confusion
def verify_token_safe(token, public_key):
try:
header = jwt.get_unverified_header(token)
if header["alg"] == "HS256":
raise ValueError("HMAC algorithm not allowed")
payload = jwt.decode(
token,
public_key,
algorithms=["RS256", "ES256"]
)
return payload
except jwt.InvalidKeyError:
raise ValueError("Invalid key")
Common Mistakes
| Mistake | Why It's Wrong | Fix |
|---|---|---|
| Using "none" algorithm | Removes signature verification | Explicitly reject "none" algorithm |
| Algorithm confusion attack | Attacker changes RS256 to HS256 using public key as secret | Always specify allowed algorithms server-side |
| Storing sensitive data in payload | JWT payload is base64-encoded, not encrypted | Never store secrets or PII in JWT payload |
| No token expiration | Stolen tokens work forever | Set short expiration (15-60 minutes) |
| Not validating all claims | Missing aud, iss, or sub checks | Validate every claim that matters to your app |
Practice Questions
- What three parts make up a JWT?
- How do you prevent algorithm confusion attacks?
- Why should you never store passwords in a JWT payload?
- What is the purpose of the jti claim?
- How does RS256 differ from HS256?
Challenge
Implement a JWT system with access tokens (15 min) and refresh tokens (7 days). Include audience and issuer validation. Test token replay by using an expired token.
FAQ
Mini Project
Build a Flask API with secure JWT authentication using RS256. Generate an RSA key pair, sign tokens with the private key, and verify with the public key. Include token blacklisting.
What's Next
Explore OAuth 2.0 flow to understand delegated authorization for third-party applications.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro