Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for csrf tokens in html forms with jinja #11658

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

LainezDev
Copy link

@LainezDev LainezDev commented May 30, 2024

Now FastAPI with support for csrf tokens in html forms with jinja

Added some improvements for using CSRF tokens in HTML forms without affecting the current functionality of the middleware starlette-csrf. The improvement simplifies the process of obtaining the CSRF token by unifying the search logic into a single function _get_submitted_csrf_token. Now, the function will first attempt to obtain the token from the headers, and if it does not find it there, it will search for it in the form. Additionally, this new functionality addresses the crash issue that occurs in Starlette due to body consumption.

This is particularly useful for integration with FastAPI and the development of secure websites utilizing forms.

Now, all that's needed is to add the starlette_csrf middleware and utilize the following template processor in your FastAPI code:

from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
from fastapi.middleware.csrf import CSRFMiddleware, csrf_token_processor

app = FastAPI()

your_cookie_name = "your_cookie_name"
your_header_name = "your_header_name"

app.add_middleware(
   CSRFMiddleware, 
   secret = "your_secret",
   cookie_name = your_cookie_name,
   header_name = your_header_name,
   )

templates = Jinja2Templates(
    directory="templates", 
    context_processors=[csrf_token_processor(your_cookie_name, your_header_name)]
    )

Now, the middleware integrates with a context processor, a function that returns a dictionary containing the CSRF token, CSRF input, and CSRF header for use with other tools such as HTMX.

Simply using {{ csrf_input | safe }} in each form is now sufficient to ensure a more secure web application. For example:

<form method="post">
    {{ csrf_input | safe }}
    <!-- Other form fields here -->
    <button type="submit">Submit</button>
</form>

Furthermore, we can use {{ csrf_header }} in HTMX requests. For example:

<form hx-patch="/route/edit" hx-headers='{{ csrf_header | tojson | safe }}'  hx-trigger="submit" hx-target="#yourtarget" hx-swap="outerHTML" >
    <!-- Other form fields here -->
    <button type="submit">Submit</button>
</form>

Feel free to let me know if you need further adjustments or improvements!

@alejsdev alejsdev added feature New feature or request p4 labels Jun 19, 2024
Copy link

@GDemay GDemay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work!

or not submitted_csrf_token
or not self._csrf_tokens_match(csrf_cookie, submitted_csrf_token)
):
response = self._get_error_response(request)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
response = self._get_error_response(request)
response = self._get_error_response(request=request)

await response(scope, receive, send)
return

request._receive = self._receive_with_body(request._receive, body)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing typing

return False

async def _get_request_body(self, request: Request):
if request.method in ("POST", "PUT", "PATCH", "DELETE"):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about adding it in a settings file?

from enum import Enum
from pydantic import BaseSettings

class HttpMethod(str, Enum):
    POST = "POST"
    PUT = "PUT"
    PATCH = "PATCH"
    DELETE = "DELETE"


class Settings(BaseSettings):
    allowed_methods: list[HttpMethod] = [
        HttpMethod.POST, 
        HttpMethod.PUT, 
        HttpMethod.PATCH, 
        HttpMethod.DELETE
    ]

settings = Settings()

decoded1: str = self.serializer.loads(token1)
decoded2: str = self.serializer.loads(token2)
return secrets.compare_digest(decoded1, decoded2)
except BadSignature:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we had a logger here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request p4
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants