FastAPI Security Headers
When building web applications with FastAPI, securing your API goes beyond authentication and authorization. HTTP security headers are a crucial layer of defense that help protect your application against various attacks like Cross-Site Scripting (XSS), clickjacking, and other common vulnerabilities.
In this guide, we'll learn how to implement and customize security headers in your FastAPI applications.
What are HTTP Security Headers?
HTTP security headers are special HTTP response headers that your application can return to instruct browsers to enable certain security mechanisms. These headers act as a set of instructions that tell browsers how to behave when handling your website's content.
Common security headers include:
- Content-Security-Policy (CSP): Controls which resources can be loaded
- X-XSS-Protection: Helps prevent cross-site scripting attacks
- X-Frame-Options: Prevents clickjacking attacks
- Strict-Transport-Security (HSTS): Forces secure (HTTPS) connections
- X-Content-Type-Options: Prevents MIME-type sniffing
- Referrer-Policy: Controls how much referrer information is included
Implementing Security Headers in FastAPI
FastAPI doesn't have built-in middleware specifically for security headers, but we can easily create our own middleware to add these headers to every response.
Basic Security Headers Middleware
Let's create a simple middleware that adds some essential security headers to all responses:
from fastapi import FastAPI, Request
from fastapi.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
# Add security headers
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
response.headers["Content-Security-Policy"] = "default-src 'self'"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=(), interest-cohort=()"
return response
# Create FastAPI app
app = FastAPI()
# Add the middleware
app.add_middleware(SecurityHeadersMiddleware)
@app.get("/")
async def root():
return {"message": "Hello World"}
This middleware adds the following security headers to all responses:
- X-Frame-Options: Prevents your page from being displayed in an iframe
- X-Content-Type-Options: Prevents the browser from interpreting files as a different MIME type
- X-XSS-Protection: Enables the browser's built-in XSS filter
- Strict-Transport-Security: Forces HTTPS connections
- Content-Security-Policy: Restricts the sources from which content can be loaded
- Referrer-Policy: Controls how much referrer information is included when navigating
- Permissions-Policy: Restricts which browser features can be used
Creating a Configurable Security Headers Middleware
For more flexibility, let's create a configurable security headers middleware that allows you to specify which headers to include and their values:
from fastapi import FastAPI, Request
from fastapi.middleware.base import BaseHTTPMiddleware
from typing import Dict, Optional
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
def __init__(
self,
app,
content_security_policy: Optional[str] = None,
x_frame_options: str = "DENY",
x_content_type_options: str = "nosniff",
x_xss_protection: str = "1; mode=block",
strict_transport_security: str = "max-age=31536000; includeSubDomains",
referrer_policy: str = "strict-origin-when-cross-origin",
permissions_policy: Optional[str] = None,
):
super().__init__(app)
self.security_headers = {}
# Add headers conditionally
if content_security_policy:
self.security_headers["Content-Security-Policy"] = content_security_policy
if x_frame_options:
self.security_headers["X-Frame-Options"] = x_frame_options
if x_content_type_options:
self.security_headers["X-Content-Type-Options"] = x_content_type_options
if x_xss_protection:
self.security_headers["X-XSS-Protection"] = x_xss_protection
if strict_transport_security:
self.security_headers["Strict-Transport-Security"] = strict_transport_security
if referrer_policy:
self.security_headers["Referrer-Policy"] = referrer_policy
if permissions_policy:
self.security_headers["Permissions-Policy"] = permissions_policy
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
# Add all configured security headers
for header_name, header_value in self.security_headers.items():
response.headers[header_name] = header_value
return response
# Create FastAPI app
app = FastAPI()
# Add the middleware with custom configuration
app.add_middleware(
SecurityHeadersMiddleware,
content_security_policy="default-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline';",
permissions_policy="camera=(), microphone=(), geolocation=()"
)
@app.get("/")
async def root():
return {"message": "Hello World"}
With this configurable middleware, you can easily adjust the security headers for your specific application needs.
Using Third-Party Packages
For more comprehensive security header management, you can use third-party packages like secure
which provides a collection of security-related headers:
from fastapi import FastAPI
from secure import SecureHeaders
# Initialize secure headers
secure_headers = SecureHeaders(
csp=True,
hsts=True,
xfo="DENY",
xxp=True,
content_type=True,
referrer=True,
permissions_policy={"camera": "()", "microphone": "()", "geolocation": "()"}
)
app = FastAPI()
@app.middleware("http")
async def set_secure_headers(request, call_next):
response = await call_next(request)
secure_headers.framework.fastapi(response)
return response
@app.get("/")
def read_root():
return {"Hello": "World"}
Understanding Security Headers in Detail
Content-Security-Policy (CSP)
The Content-Security-Policy header is one of the most powerful security headers. It allows you to specify which resources can be loaded and from where.
# A basic CSP that only allows resources from the same origin
response.headers["Content-Security-Policy"] = "default-src 'self'"
# A more permissive CSP allowing images from anywhere and scripts from specific domains
response.headers["Content-Security-Policy"] = "default-src 'self'; img-src *; script-src 'self' https://trusted-cdn.com"
X-Frame-Options
The X-Frame-Options header helps prevent clickjacking attacks by controlling whether a page can be embedded in an iframe, frame, or object.
# Prevent any site from framing the content
response.headers["X-Frame-Options"] = "DENY"
# Allow the page to be framed by pages from the same origin
response.headers["X-Frame-Options"] = "SAMEORIGIN"
# Allow the page to be framed by a specific site
response.headers["X-Frame-Options"] = "ALLOW-FROM https://trusted-site.com"
Strict-Transport-Security (HSTS)
HSTS forces browsers to use HTTPS instead of HTTP, protecting against protocol downgrade attacks and cookie hijacking.
# Enable HSTS with a 1-year max-age
response.headers["Strict-Transport-Security"] = "max-age=31536000"
# Include subdomains in the HSTS policy
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
# Enable preloading (only use if you're submitting to the HSTS preload list)
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains; preload"
Real-World Example: A FastAPI Application with Comprehensive Security Headers
Let's create a more complete example of a FastAPI application with security headers:
from fastapi import FastAPI, Request, Response
from fastapi.middleware.base import BaseHTTPMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from typing import List, Dict, Optional
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
def __init__(self, app, csp_directives: Optional[Dict[str, str]] = None):
super().__init__(app)
self.csp_directives = csp_directives or {
"default-src": "'self'",
"img-src": "'self' data:",
"script-src": "'self'",
"style-src": "'self' 'unsafe-inline'",
"font-src": "'self'",
"connect-src": "'self'",
"frame-ancestors": "'none'",
"form-action": "'self'",
"base-uri": "'self'",
"object-src": "'none'"
}
def _build_csp_header(self) -> str:
return "; ".join([f"{key} {value}" for key, value in self.csp_directives.items()])
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
# Add security headers
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
response.headers["Content-Security-Policy"] = self._build_csp_header()
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=(), interest-cohort=()"
response.headers["Cache-Control"] = "no-store, max-age=0"
return response
# Sample book data model
class Book(BaseModel):
id: int
title: str
author: str
# Create FastAPI app
app = FastAPI(
title="Secure Book API",
description="A sample FastAPI application with security headers",
version="1.0.0"
)
# Add security headers middleware
app.add_middleware(
SecurityHeadersMiddleware,
csp_directives={
"default-src": "'self'",
"img-src": "'self' data:",
"script-src": "'self' 'unsafe-inline'", # Allowing inline scripts for Swagger UI
"style-src": "'self' 'unsafe-inline'", # Allowing inline styles for Swagger UI
"frame-ancestors": "'none'",
"form-action": "'self'",
"base-uri": "'self'",
"object-src": "'none'"
}
)
# Sample data
books = [
Book(id=1, title="The Great Gatsby", author="F. Scott Fitzgerald"),
Book(id=2, title="1984", author="George Orwell"),
Book(id=3, title="To Kill a Mockingbird", author="Harper Lee"),
]
@app.get("/", response_class=JSONResponse)
async def root():
return {"message": "Welcome to the Secure Book API"}
@app.get("/books", response_model=List[Book])
async def get_books():
return books
@app.get("/books/{book_id}", response_model=Optional[Book])
async def get_book(book_id: int):
for book in books:
if book.id == book_id:
return book
return JSONResponse(status_code=404, content={"message": "Book not found"})
When you run this application and inspect the HTTP response headers in your browser's developer tools, you'll see that all the security headers are correctly applied.
Testing Your Security Headers
After implementing security headers, it's crucial to verify they're working correctly. You can use online tools like:
These tools scan your website and provide a security grade based on your implemented headers.
To test locally, you can use curl:
curl -I http://localhost:8000
This will show you all the response headers from your FastAPI application.
Best Practices for Security Headers
- Start Strict and Loosen as Needed: Begin with the most restrictive settings and relax them only when necessary
- Test Thoroughly: Security headers can break functionality if not implemented correctly
- Stay Updated: Security best practices evolve; regularly review and update your security headers
- Use Content-Security-Policy: CSP is one of the most effective defenses against XSS attacks
- Consider Your Application's Needs: Not all headers are appropriate for all applications
Summary
HTTP security headers are a vital component of web application security. With FastAPI, implementing these headers is straightforward through middleware. A properly configured set of security headers can significantly enhance your application's security posture with minimal effort.
In this guide, we've covered:
- What HTTP security headers are and why they're important
- How to implement basic and configurable security headers middleware in FastAPI
- Detailed explanations of key security headers
- A real-world example of a FastAPI application with comprehensive security headers
- How to test your security headers
By implementing these security headers in your FastAPI applications, you can help protect your users from common web vulnerabilities and attacks.
Additional Resources
- OWASP Secure Headers Project
- Mozilla's Web Security Guidelines
- Content Security Policy Reference
- Scott Helme's Security Headers Blog
Exercises
- Implement a basic security headers middleware in a FastAPI application and test it using curl or browser developer tools.
- Create a custom Content-Security-Policy that allows resources from your application and a specific CDN.
- Modify the configurable middleware to allow specific headers to be excluded for certain routes or path patterns.
- Use the
secure
package to implement security headers and compare its functionality with your custom middleware. - Run your FastAPI application with security headers through an online security headers testing tool and make improvements based on the results.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)