fastapi
fastapi copied to clipboard
Add security headers as middlewares
First Check
- [X] I added a very descriptive title to this issue.
- [X] I used the GitHub search to find a similar issue and didn't find it.
- [X] I searched the FastAPI documentation, with the integrated search.
- [X] I already searched in Google "How to X in FastAPI" and didn't find any information.
- [X] I already read and followed all the tutorial in the docs and didn't find an answer.
- [X] I already checked if it is not related to FastAPI but to Pydantic.
- [X] I already checked if it is not related to FastAPI but to Swagger UI.
- [X] I already checked if it is not related to FastAPI but to ReDoc.
Commit to Help
- [X] I commit to help with one of those options 👆
Example Code
...
Description
Add couple of security middleware, which, I think, must be built-in app
Wanted Solution
Implement middlewares from https://github.com/tmotagam/Secweb : Required (my opinion):
- Content Security Policy (CSP)
- Referrer Policy
- HTTP Strict Transport Security(HSTS)
- X-XSS-Protection
Other - as you want (my opinion)
- ExpectCT
- Origin Agent Cluster
- X-Content-Type-Options
- X-DNS-Prefetch-Control
- X-Download-Options
- X-Frame
- X-Permitted-Cross-Domain-Policies
- Cross-Origin-Embedder-Policy
- Cross-Origin-Opener-Policy
- Cross-Origin-Resource-Policy
Wanted Code
from fastapi import FastAPI
from fastapi.middleware import ContentSecurityPolicy # or other middleware
app = FastAPI()
app.add_middleware(ContentSecurityPolicy, Option=...)
Alternatives
https://github.com/tmotagam/Secweb
Operating System
Linux, Windows, macOS
Operating System Details
No response
FastAPI Version
latest
Python Version
3.10.1
Additional Context
No response
@Niccolum That's a great suggestion and I can work on this enhancement, but I think it would be more appropriate to do it on starlette and then import the packages here, as it's already implemented nowadays. For instance:
cors.py
from starlette.middleware.cors import CORSMiddleware as CORSMiddleware # noqa
@lucastosetto Starlette maintainer said, that he see this only as 3rd party library.
I came across this issue while searching something else, but I implemented such a middleware in a proprietary project.
You may or may not take this code as a starting point if you wish:
"""Middleware for security."""
from collections import OrderedDict
from fastapi import FastAPI, Request, Response
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
CSP: dict[str, str | list[str]] = {
"default-src": "'self'",
"img-src": [
"*",
# For SWAGGER UI
"data:",
],
"connect-src": "'self'",
"script-src": "'self'",
"style-src": ["'self'", "'unsafe-inline'"],
"script-src-elem": [
# For SWAGGER UI
"https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui-bundle.js",
"'sha256-1I8qOd6RIfaPInCv8Ivv4j+J0C6d7I8+th40S5U/TVc='",
],
"style-src-elem": [
# For SWAGGER UI
"https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui.css",
],
}
def parse_policy(policy: dict[str, str | list[str]] | str) -> str:
"""Parse a given policy dict to string."""
if isinstance(policy, str):
# parse the string into a policy dict
policy_string = policy
policy = OrderedDict()
for policy_part in policy_string.split(";"):
policy_parts = policy_part.strip().split(" ")
policy[policy_parts[0]] = " ".join(policy_parts[1:])
policies = []
for section, content in policy.items():
if not isinstance(content, str):
content = " ".join(content)
policy_part = f"{section} {content}"
policies.append(policy_part)
parsed_policy = "; ".join(policies)
return parsed_policy
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
"""Add security headers to all responses."""
def __init__(self, app: FastAPI, csp: bool = True) -> None:
"""Init SecurityHeadersMiddleware.
:param app: FastAPI instance
:param no_csp: If no CSP should be used;
defaults to :py:obj:`False`
"""
super().__init__(app)
self.csp = csp
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
"""Dispatch of the middleware.
:param request: Incoming request
:param call_next: Function to process the request
:return: Return response coming from from processed request
"""
headers = {
"Content-Security-Policy": "" if not self.csp else parse_policy(CSP),
"Cross-Origin-Opener-Policy": "same-origin",
"Referrer-Policy": "strict-origin-when-cross-origin",
"Strict-Transport-Security": "max-age=31556926; includeSubDomains",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"X-XSS-Protection": "1; mode=block",
}
response = await call_next(request)
response.headers.update(headers)
return response
You can add the middleware the default way like:
app = FastAPI()
app.add_middleware(SecurityHeadersMiddleware, csp=True)
This code needs python 3.10+ because of the type hints only.
What is the current status of this issue? Is there a plan to add such feature? If there is no, I guess there are two options to implement such security check: Use the alternative https://github.com/tmotagam/Secweb (as suggested) or implement it yourself
Any comment is appreciated !!
It sounds like many are running into the same issue, what did you end up using @mert-kurttutan and @Niccolum?
@staubina i used library secweb, but I don't really like its implementation