JWT Security Headers — Protecting Token-Based APIs with HTTP Security Headers
In this tutorial, you will learn about JWT Security Headers. We cover key concepts, practical examples, and best practices to help you master this topic.
JWT security headers protect token transmission and storage by enforcing HTTPS, preventing MIME sniffing, controlling resource loading, and setting secure cookie attributes for token-bearing requests.
What You'll Learn
- HTTP security headers for JWT-protected APIs
- Secure cookie configuration for refresh tokens
- CSP directives to prevent token exfiltration
- HSTS preload and certificate transparency
- Custom headers for token fingerprinting
Why It Matters
Security headers prevent entire classes of attacks. A missing Strict-Transport-Security header enables SSL stripping. A missing Content-Security-Policy allows token exfiltration via XSS. DodaTech's API gateway enforces 14 security headers, blocking token theft attempts at the network layer.
Real-World Use
A dashboard application uses JWTs for authentication. The API sets HSTS, CSP with connect-src restrictions, and X-Content-Type-Options: nosniff. When an XSS vulnerability was discovered, the CSP prevented attackers from exfiltrating tokens to external servers.
flowchart LR
A["Browser"] --> B["API Gateway"]
B --> C["Security Header Middleware"]
C --> D["Strict-Transport-Security"]
C --> E["Content-Security-Policy"]
C --> F["X-Content-Type-Options"]
C --> G["X-Frame-Options"]
C --> H["Set-Cookie
HttpOnly, Secure, SameSite"]
D --> I["Resource Server"]
Code Examples
Example 1: Security Header Middleware
from flask import Flask, request, jsonify, make_response
app = Flask(__name__)
SECURITY_HEADERS = {
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '0',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'geolocation=(), microphone=(), camera=()',
'Cache-Control': 'no-store'
}
@app.after_request
def add_security_headers(response):
for header, value in SECURITY_HEADERS.items():
response.headers[header] = value
return response
@app.route('/api/protected')
def protected_route():
# Token validation happens here
return jsonify({'message': 'Protected resource'})
Example 2: Secure Cookie Configuration for Refresh Tokens
from flask import make_response, jsonify
import jwt
from datetime import datetime, timedelta, timezone
def issue_tokens(user_id, private_key):
access_token = create_access_token(user_id, private_key)
refresh_token = create_refresh_token(user_id, private_key)
response = make_response(jsonify({
'access_token': access_token,
'token_type': 'Bearer',
'expires_in': 900
}))
# Set refresh token as httpOnly cookie
response.set_cookie(
'refresh_token',
value=refresh_token,
httponly=True,
secure=True,
samesite='Strict',
max_age=7 * 24 * 3600,
path='/api/auth/refresh',
domain='.dodatech.com'
)
# Add cache control for token responses
response.headers['Cache-Control'] = 'no-store'
response.headers['Pragma'] = 'no-cache'
return response
# Usage
response = issue_tokens('user_123', private_key)
# Cookie is automatically httpOnly, Secure, SameSite=Strict
Example 3: CSP for Token Protection
def generate_csp():
script_src = ["'self'", "'strict-dynamic'", "'nonce-{random}'"]
connect_src = ["'self'", "https://api.dodatech.com"]
form_action = ["'self'"]
base_uri = ["'none'"]
csp = "; ".join([
f"default-src 'self'",
f"script-src {' '.join(script_src)}",
f"connect-src {' '.join(connect_src)}",
f"form-action {' '.join(form_action)}",
f"base-uri {' '.join(base_uri)}",
"object-src 'none'",
"frame-ancestors 'none'"
])
return csp
@app.after_request
def add_csp(response):
nonce = secrets.token_hex(16)
csp = generate_csp().replace('{random}', nonce)
response.headers['Content-Security-Policy'] = csp
return response
# The CSP prevents token exfiltration by restricting
# where content can be loaded from and sent to
Common Mistakes
1. Missing HSTS Header
Without HSTS, an attacker with network access can downgrade HTTPS to HTTP and steal tokens.
2. CSP with 'unsafe-inline'
Inline scripts can bypass CSP. Use nonces or hashes instead of 'unsafe-inline'.
3. Cookies Without SameSite Attribute
Without SameSite=Strict, CSRF Attacks can use the user's cookies to make authenticated requests.
4. Sending Tokens in URL Parameters
URLs are logged by proxies and servers, exposing tokens. Always send tokens in headers.
5. Caching Token Responses
Set Cache-Control: no-store on any endpoint that returns tokens.
Practice Questions
- What does Strict-Transport-Security prevent?
- Why should refresh tokens be in httpOnly cookies?
- What CSP directive controls where API requests can be sent?
- How does SameSite=Strict prevent CSRF?
- Why disable caching for token responses?
Answers:
- Prevents SSL stripping attacks by forcing browsers to always use HTTPS.
- httpOnly cookies are inaccessible to JavaScript, preventing XSS-based token theft.
connect-srccontrols which URLs JavaScript can make network requests to.- The browser only sends SameSite=Strict cookies for same-site requests, blocking cross-site request forgery.
- Cached token responses could be served from disk to an attacker with local access.
Challenge: Configure a complete security headers middleware for a JWT-protected API. Include HSTS preload, CSP with nonces, and secure cookie configuration. Test with securityheaders.com.
FAQ
What's Next
Apply these headers to your {{< ilink "JWT" "JWT Authentication Service" }}, and review {{< ilink "JWT" "JWT Best Practices" }} for additional production hardening.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro