What is JWT?
JWT (JSON Web Token) is a compact, URL-safe way to represent claims between two parties. It's commonly used for authentication and information exchange in web applications.
Think of a JWT like a movie ticket. The ticket contains information about you (seat number, movie, time), is signed by the theater (so it can't be forged), and is presented at the door to prove you're allowed in.
Why Use JWT?
Traditional session-based authentication stores user info on the server. JWT stores it in the token itself.
Session-Based Auth (Traditional)
1. User logs in with username/password
2. Server creates session, stores in database
3. Server sends session ID in cookie
4. Client sends session ID with each request
5. Server looks up session in database to verify
Problem: Server must store all sessions
Problem: Doesn't scale well across multiple servers
Token-Based Auth (JWT)
1. User logs in with username/password
2. Server creates JWT with user info, signs it
3. Server sends JWT to client
4. Client stores JWT and sends with each request
5. Server verifies signature - no database lookup needed!
Advantage: Stateless - server doesn't store anything
Advantage: Scales easily across multiple servers
JWT Structure
A JWT consists of three parts separated by dots:
xxxxx.yyyyy.zzzzz
│ │ │
│ │ └── Signature
│ └── Payload
└── Header
Example JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
1. Header
Contains the token type and signing algorithm.
// Decoded header
{
"alg": "HS256", // Algorithm used
"typ": "JWT" // Token type
}
2. Payload
Contains the claims - statements about the user and additional data.
// Decoded payload
{
"sub": "1234567890", // Subject (user ID)
"name": "John Doe", // Custom claim
"email": "john@example.com",
"role": "admin",
"iat": 1516239022, // Issued at (timestamp)
"exp": 1516242622 // Expiration (timestamp)
}
3. Signature
Created by encoding header and payload, then signing with a secret key.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-secret-key
)
JWT in Python
Using the PyJWT library:
# Install PyJWT
pip install PyJWT
# Creating a JWT
import jwt
from datetime import datetime, timedelta
SECRET_KEY = "your-secret-key-keep-it-safe"
def create_token(user_id: int, username: str) -> str:
payload = {
"sub": user_id, # Subject (user identifier)
"username": username,
"iat": datetime.utcnow(), # Issued at
"exp": datetime.utcnow() + timedelta(hours=24) # Expires in 24 hours
}
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
return token
# Verifying a JWT
def verify_token(token: str) -> dict:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return payload
except jwt.ExpiredSignatureError:
raise Exception("Token has expired")
except jwt.InvalidTokenError:
raise Exception("Invalid token")
# Usage
token = create_token(123, "john_doe")
print(f"Token: {token}")
data = verify_token(token)
print(f"User ID: {data['sub']}, Username: {data['username']}")
JWT with FastAPI
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel
import jwt
from datetime import datetime, timedelta
app = FastAPI()
security = HTTPBearer()
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
class LoginRequest(BaseModel):
username: str
password: str
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
# Fake user database
fake_users = {
"john": {"password": "secret123", "user_id": 1}
}
def create_access_token(user_id: int, username: str) -> str:
payload = {
"sub": user_id,
"username": username,
"exp": datetime.utcnow() + timedelta(hours=24)
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
token = credentials.credentials
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token has expired"
)
except jwt.InvalidTokenError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
@app.post("/login", response_model=TokenResponse)
def login(request: LoginRequest):
user = fake_users.get(request.username)
if not user or user["password"] != request.password:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid username or password"
)
token = create_access_token(user["user_id"], request.username)
return TokenResponse(access_token=token)
@app.get("/protected")
def protected_route(current_user: dict = Depends(get_current_user)):
return {
"message": f"Hello, {current_user['username']}!",
"user_id": current_user["sub"]
}
@app.get("/profile")
def get_profile(current_user: dict = Depends(get_current_user)):
return {
"user_id": current_user["sub"],
"username": current_user["username"]
}
Access Tokens vs Refresh Tokens
A common pattern uses two types of tokens:
Access Token
- Short-lived (15 minutes to 1 hour)
- Used for API requests
- Contains user info
Refresh Token
- Long-lived (days to weeks)
- Used only to get new access tokens
- Stored securely (httpOnly cookie)
def create_tokens(user_id: int, username: str):
# Access token - short lived
access_payload = {
"sub": user_id,
"username": username,
"type": "access",
"exp": datetime.utcnow() + timedelta(minutes=15)
}
access_token = jwt.encode(access_payload, SECRET_KEY, algorithm="HS256")
# Refresh token - long lived
refresh_payload = {
"sub": user_id,
"type": "refresh",
"exp": datetime.utcnow() + timedelta(days=7)
}
refresh_token = jwt.encode(refresh_payload, SECRET_KEY, algorithm="HS256")
return access_token, refresh_token
@app.post("/refresh")
def refresh_access_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
token = credentials.credentials
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
if payload.get("type") != "refresh":
raise HTTPException(status_code=401, detail="Invalid token type")
# Create new access token
new_access_token = create_access_token(payload["sub"], payload.get("username", ""))
return {"access_token": new_access_token}
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Refresh token expired")
JWT Security Best Practices
- Use HTTPS: Always transmit tokens over encrypted connections.
- Keep secrets secret: Never expose your secret key. Use environment variables.
- Set expiration: Always include an exp claim. Short-lived tokens are safer.
- Validate all claims: Check exp, iss (issuer), aud (audience) as needed.
- Use strong algorithms: HS256 minimum, RS256 for distributed systems.
- Don't store sensitive data: Payloads can be decoded. Don't include passwords!
- Store tokens safely: In browsers, use httpOnly cookies or secure storage.
- Implement token revocation: Have a way to invalidate tokens if needed.
# BAD - Secret in code
SECRET_KEY = "my-secret-key"
# GOOD - Secret from environment
import os
SECRET_KEY = os.environ.get("JWT_SECRET_KEY")
# BAD - Sensitive data in payload
payload = {
"user_id": 123,
"password": "secret123", # NEVER DO THIS!
"credit_card": "1234..." # NEVER DO THIS!
}
# GOOD - Only necessary identifiers
payload = {
"sub": 123, # User ID
"role": "admin",
"exp": datetime.utcnow() + timedelta(hours=1)
}
Common JWT Claims
{
// Registered claims (standard)
"iss": "your-app", // Issuer
"sub": "user-123", // Subject (user ID)
"aud": "your-api", // Audience
"exp": 1516242622, // Expiration time
"nbf": 1516239022, // Not valid before
"iat": 1516239022, // Issued at
"jti": "unique-token-id", // JWT ID (for revocation)
// Public claims (custom but registered)
"name": "John Doe",
"email": "john@example.com",
// Private claims (your own)
"role": "admin",
"permissions": ["read", "write"]
}
JWT vs Sessions: When to Use What
Use JWT when:
- Building APIs consumed by mobile apps or SPAs
- Need stateless authentication
- Microservices architecture
- Cross-domain authentication needed
Use Sessions when:
- Traditional server-rendered web apps
- Need immediate token revocation
- Smaller scale, single server
- Less complexity needed
Master JWT with Expert Mentorship
Our Full Stack Python program covers JWT authentication in depth. Learn to build secure APIs with proper authentication using FastAPI and Django with personalized guidance.
Explore Full Stack Python Program