fastapi icon indicating copy to clipboard operation
fastapi copied to clipboard

Add security headers as middlewares

Open Niccolum opened this issue 3 years ago • 6 comments

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 avatar Jan 12 '22 20:01 Niccolum

@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 avatar Jan 21 '22 10:01 lucastosetto

@lucastosetto Starlette maintainer said, that he see this only as 3rd party library.

Niccolum avatar Feb 03 '22 15:02 Niccolum

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.

Cielquan avatar Sep 01 '22 11:09 Cielquan

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 !!

mert-kurttutan avatar Jan 08 '23 09:01 mert-kurttutan

It sounds like many are running into the same issue, what did you end up using @mert-kurttutan and @Niccolum?

staubina avatar Jan 11 '23 12:01 staubina

@staubina i used library secweb, but I don't really like its implementation

Niccolum avatar Jan 11 '23 13:01 Niccolum