# FastAPI CSRF Protect
[](https://pypi.org/project/fastapi-csrf-protect)
[](https://pypi.org/project/fastapi-csrf-protect)
[](https://pypi.org/project/fastapi-csrf-protect)
[](https://pypi.org/project/fastapi-csrf-protect)
[](.)
[](.)
[](.)
[](.)
[](https://aekasitt.github.io/fastapi-csrf-protect)
[](https://github.com/aekasitt/fastapi-csrf-protect/blob/master/static/protect-banner.svg)
## Features
FastAPI extension that provides stateless Cross-Site Request Forgery (XSRF) Protection support.
Aimed to be easy to use and lightweight, we adopt [Double Submit Cookie](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie) mitigation pattern.
If you were familiar with `flask-wtf` library this extension suitable for you.
This extension inspired by `fastapi-jwt-auth` 😀
- Storing `fastapi-csrf-token` in cookies or serve it in template's context
## Installation
The easiest way to start working with this extension with pip
```bash
pip install fastapi-csrf-protect
# or
uv add fastapi-csrf-protect
```
## Getting Started
The following examples show you how to integrate this extension to a FastAPI App
### Example Login Form
```python
from fastapi import FastAPI, Request, Depends
from fastapi.responses import JSONResponse
from fastapi.templating import Jinja2Templates
from fastapi_csrf_protect import CsrfProtect
from fastapi_csrf_protect.exceptions import CsrfProtectError
from pydantic_settings import BaseSettings
app = FastAPI()
templates = Jinja2Templates(directory="templates")
class CsrfSettings(BaseSettings):
secret_key: str = "asecrettoeverybody"
cookie_samesite: str = "none"
@CsrfProtect.load_config
def get_csrf_config():
return CsrfSettings()
@app.get("/login")
def form(request: Request, csrf_protect: CsrfProtect = Depends()):
"""
Returns form template.
"""
csrf_token, signed_token = csrf_protect.generate_csrf_tokens()
response = templates.TemplateResponse(
"form.html", {"request": request, "csrf_token": csrf_token}
)
csrf_protect.set_csrf_cookie(signed_token, response)
return response
@app.post("/login", response_class=JSONResponse)
async def create_post(request: Request, csrf_protect: CsrfProtect = Depends()):
"""
Creates a new Post
"""
await csrf_protect.validate_csrf(request)
response: JSONResponse = JSONResponse(status_code=200, content={"detail": "OK"})
csrf_protect.unset_csrf_cookie(response) # prevent token reuse
return response
@app.exception_handler(CsrfProtectError)
def csrf_protect_exception_handler(request: Request, exc: CsrfProtectError):
return JSONResponse(status_code=exc.status_code, content={"detail": exc.message})
```
### How to send the CSRF token in your client code
#### HTML Form (Server-side rendered)
```html
<form method="post" action="/login">
<input type="hidden" name="token_key" value="{{ csrf_token }}">
<!-- other fields -->
</form>
```
#### AJAX (JavaScript)
```javascript
fetch("/items/123", {
method: "DELETE",
headers: {
"X-CSRFToken": getCookie("csrftoken")
},
credentials: "include"
});
```
> [!IMPORTANT]
> - The flexible sub-package ignores the token_location setting — tokens from either header or body are always accepted.
> - CSRF token validation still requires a matching CSRF cookie as in the base package.
> - Priority is given to header over body when both are present.
### 📌 Flexible Mode (fastapi_csrf_protect.flexible)
Some applications combine **Server-Side Rendering (SSR)** with **API endpoints** in the same project.
For example:
- **SSR pages** rendered with Jinja2 templates that use HTML forms (CSRF token in **form body**)
- **AJAX / API calls** (e.g. DELETE, PUT, PATCH) that pass the CSRF token in the **HTTP header**
The main fastapi-csrf-protect package is **opinionated** and expects the CSRF token in **one location only** (either header or body).
For hybrid apps, this can be inconvenient.
The **flexible sub-package** provides a drop-in replacement for CsrfProtect that **always accepts CSRF tokens from either the header or the form body**, with the following priority:
- **Header**: X-CSRFToken
- **Body**: token_key (form-data)
### When to use flexible
Use fastapi_csrf_protect.flexible if:
- You have both SSR pages and API endpoints in the same project.
- Some requests (like DELETE) cannot send a body but still require CSRF validation.
- You want to avoid maintaining two different CSRF configurations.
If your app only uses **one** method to send CSRF tokens, stick to the **core package** for a stricter policy.
## Contributions
### Prerequisites
* [git](https://git-scm.com/) - --fast-version-control
* [python](https://www.python.org) 3.9 and above - High-level general-purpose programming language
* [uv](https://docs.astral.sh/uv) - Extremely fast Python package & project manager, written in Rust
The following guide walks through setting up your local working environment using `git`
as distributed version control system and `uv` as Python package and version manager.
If you do not have `git` installed, run the following command.
<details>
<summary> Install using Homebrew (Darwin) </summary>
```bash
brew install git
```
</details>
<details>
<summary> Install via binary installer (Linux) </summary>
* Debian-based package management
```bash
sudo apt install git-all
```
* Fedora-based package management
```bash
sudo dnf install git-all
```
</details>
If you do not have `uv` installed, run the following command.
<details>
<summary> Install using Homebrew (Darwin) </summary>
```bash
brew install uv
```
</details>
<details>
<summary> Install using standalone installer (Darwin and Linux) </summary>
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
</details>
Once you have `git` distributed version control system installed, you can
clone the current repository and install any version of Python above version
3.9 for this project. The following commands help you set up and activate a
Python virtual environment where `uv` can download project dependencies from the `PyPI`
open-sourced registry defined under `pyproject.toml` file.
<details>
<summary> Set up environment and synchronize project dependencies </summary>
```bash
git clone git@github.com:aekasitt/fastapi-csrf-protect.git
cd fastapi-csrf-protect
uv venv --python 3.9.6
source .venv/bin/activate
uv sync --dev
```
</details>
### Getting started
To contribute to the project, fork the repository and clone to your local device
and install preferred testing dependency [pytest](https://github.com/pytest-dev/pytest)
Alternatively, run the following command on your terminal to do so:
```bash
uv sync --dev
```
Testing can be done by the following command post-installation:
```bash
uv sync --dev --group tests
pytest
```
## Change-logs
* **0.3.1** Adopt [Double Submit Cookie](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie)
:construction: **BREAKING CHANGE**
0.3.0 -> 0.3.1: `generate_csrf` marked for deprecation
* **0.3.2** Add `token_location` config (either `body` or `header`); Unset to prevent token reuse
:construction: **BREAKING CHANGE**
0.3.1 -> 0.3.2: `validate_csrf` is now async
* **0.3.5** Introduced Pydantic V2 related bug fixed in version 0.3.6; Affects `cookie_samesite`
* **0.3.6** Fixed `cookie_samesite` validation bug introduced in previous version
* **1.0.0** Remove deprecated `generate_csrf`, please use `generate_csrf_tokens` returning tuple
* **1.0.1** Fix cookie unsetting when configuring lib with cookie `Secure` and / or `SameSite=None`
* **1.0.2** Improve boolean handling for `LoadConfig`
* **1.0.3** Failed experiement to integrate `mypyc` compilation due to dependency injection pattern
* **1.0.4** Added flexible mode when `token_location` is omitted and multiple location checks
:construction: **FAILED ROLLOUT**
1.0.3 -> 1.0.4: Rolled out with WIP code; immediately deleted version from PyPI
* **1.0.5** Remove `@dataclass` leftover from failed experiment; Clarify failure reasons under tests
* **1.0.6** Fix `Stream consumed` when submitted tokens via form data, `isinstance` consumes body
### Run Examples
To run the provided examples, first you must install extra dependencies
[granian](https://github.com/emmett-framework/granian) and [minijinja](https://github.com/mitsuhiko/minijinja/tree/main/minijinja-py)
Alternatively, run the following command on your terminal to do so
```bash
uv sync --group=examples
```
Running the example utilizing form submission
```bash
granian --interface asgi examples.body:app
```
Running the example utilizing headers via JavaScript
```bash
granian --interface asgi examples.header:app
```
## License
This project is licensed under the terms of the MIT license.
Raw data
{
"_id": null,
"home_page": null,
"name": "fastapi-csrf-protect",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "asynchronous, cross-site request forgery, csrf, fastapi, samesite, starlette, xsrf",
"author": null,
"author_email": "Sitt Guruvanich <aekazitt+github@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/22/d6/29c3185d8c91cc6beb9711fcfb48b54ecb6ae71a755960516709f9bb6231/fastapi_csrf_protect-1.0.6.tar.gz",
"platform": null,
"description": "# FastAPI CSRF Protect\n\n[](https://pypi.org/project/fastapi-csrf-protect)\n[](https://pypi.org/project/fastapi-csrf-protect)\n[](https://pypi.org/project/fastapi-csrf-protect)\n[](https://pypi.org/project/fastapi-csrf-protect)\n[](.)\n[](.)\n[](.)\n[](.)\n[](https://aekasitt.github.io/fastapi-csrf-protect)\n\n[](https://github.com/aekasitt/fastapi-csrf-protect/blob/master/static/protect-banner.svg)\n\n## Features\n\nFastAPI extension that provides stateless Cross-Site Request Forgery (XSRF) Protection support.\nAimed to be easy to use and lightweight, we adopt [Double Submit Cookie](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie) mitigation pattern.\nIf you were familiar with `flask-wtf` library this extension suitable for you.\nThis extension inspired by `fastapi-jwt-auth` \ud83d\ude00\n\n- Storing `fastapi-csrf-token` in cookies or serve it in template's context\n\n## Installation\n\nThe easiest way to start working with this extension with pip\n\n```bash\npip install fastapi-csrf-protect\n# or\nuv add fastapi-csrf-protect\n```\n\n## Getting Started\n\nThe following examples show you how to integrate this extension to a FastAPI App\n\n### Example Login Form\n\n```python\nfrom fastapi import FastAPI, Request, Depends\nfrom fastapi.responses import JSONResponse\nfrom fastapi.templating import Jinja2Templates\nfrom fastapi_csrf_protect import CsrfProtect\nfrom fastapi_csrf_protect.exceptions import CsrfProtectError\nfrom pydantic_settings import BaseSettings\n\napp = FastAPI()\ntemplates = Jinja2Templates(directory=\"templates\")\n\nclass CsrfSettings(BaseSettings):\n secret_key: str = \"asecrettoeverybody\"\n cookie_samesite: str = \"none\"\n\n@CsrfProtect.load_config\ndef get_csrf_config():\n return CsrfSettings()\n\n@app.get(\"/login\")\ndef form(request: Request, csrf_protect: CsrfProtect = Depends()):\n \"\"\"\n Returns form template.\n \"\"\"\n csrf_token, signed_token = csrf_protect.generate_csrf_tokens()\n response = templates.TemplateResponse(\n \"form.html\", {\"request\": request, \"csrf_token\": csrf_token}\n )\n csrf_protect.set_csrf_cookie(signed_token, response)\n return response\n\n@app.post(\"/login\", response_class=JSONResponse)\nasync def create_post(request: Request, csrf_protect: CsrfProtect = Depends()):\n \"\"\"\n Creates a new Post\n \"\"\"\n await csrf_protect.validate_csrf(request)\n response: JSONResponse = JSONResponse(status_code=200, content={\"detail\": \"OK\"})\n csrf_protect.unset_csrf_cookie(response) # prevent token reuse\n return response\n\n@app.exception_handler(CsrfProtectError)\ndef csrf_protect_exception_handler(request: Request, exc: CsrfProtectError):\n return JSONResponse(status_code=exc.status_code, content={\"detail\": exc.message})\n\n```\n\n### How to send the CSRF token in your client code\n\n#### HTML Form (Server-side rendered)\n\n```html\n<form method=\"post\" action=\"/login\">\n <input type=\"hidden\" name=\"token_key\" value=\"{{ csrf_token }}\">\n <!-- other fields -->\n</form>\n```\n#### AJAX (JavaScript)\n\n```javascript\nfetch(\"/items/123\", {\n method: \"DELETE\",\n headers: {\n \"X-CSRFToken\": getCookie(\"csrftoken\")\n },\n credentials: \"include\"\n});\n```\n\n> [!IMPORTANT]\n> - The flexible sub-package ignores the token_location setting \u2014 tokens from either header or body are always accepted.\n> - CSRF token validation still requires a matching CSRF cookie as in the base package.\n> - Priority is given to header over body when both are present.\n\n### \ud83d\udccc Flexible Mode (fastapi_csrf_protect.flexible)\n\nSome applications combine **Server-Side Rendering (SSR)** with **API endpoints** in the same project.\nFor example:\n - **SSR pages** rendered with Jinja2 templates that use HTML forms (CSRF token in **form body**)\n - **AJAX / API calls** (e.g. DELETE, PUT, PATCH) that pass the CSRF token in the **HTTP header**\n\nThe main fastapi-csrf-protect package is **opinionated** and expects the CSRF token in **one location only** (either header or body).\nFor hybrid apps, this can be inconvenient.\n\nThe **flexible sub-package** provides a drop-in replacement for CsrfProtect that **always accepts CSRF tokens from either the header or the form body**, with the following priority:\n - **Header**: X-CSRFToken\n - **Body**: token_key (form-data)\n\n### When to use flexible\n\nUse fastapi_csrf_protect.flexible if:\n - You have both SSR pages and API endpoints in the same project.\n - Some requests (like DELETE) cannot send a body but still require CSRF validation.\n - You want to avoid maintaining two different CSRF configurations.\n\nIf your app only uses **one** method to send CSRF tokens, stick to the **core package** for a stricter policy.\n## Contributions\n\n### Prerequisites\n\n* [git](https://git-scm.com/) - --fast-version-control\n* [python](https://www.python.org) 3.9 and above - High-level general-purpose programming language\n* [uv](https://docs.astral.sh/uv) - Extremely fast Python package & project manager, written in Rust\n\nThe following guide walks through setting up your local working environment using `git`\nas distributed version control system and `uv` as Python package and version manager.\nIf you do not have `git` installed, run the following command.\n\n<details>\n <summary> Install using Homebrew (Darwin) </summary>\n \n ```bash\n brew install git\n ```\n</details>\n\n<details>\n <summary> Install via binary installer (Linux) </summary>\n \n * Debian-based package management\n ```bash\n sudo apt install git-all\n ```\n\n * Fedora-based package management\n ```bash\n sudo dnf install git-all\n ```\n</details>\n\nIf you do not have `uv` installed, run the following command.\n\n<details>\n <summary> Install using Homebrew (Darwin) </summary>\n\n ```bash\n brew install uv\n ```\n</details>\n\n<details>\n <summary> Install using standalone installer (Darwin and Linux) </summary>\n\n ```bash\n curl -LsSf https://astral.sh/uv/install.sh | sh\n ```\n</details>\n\nOnce you have `git` distributed version control system installed, you can\nclone the current repository and install any version of Python above version\n3.9 for this project. The following commands help you set up and activate a\nPython virtual environment where `uv` can download project dependencies from the `PyPI`\nopen-sourced registry defined under `pyproject.toml` file.\n\n<details>\n <summary> Set up environment and synchronize project dependencies </summary>\n\n ```bash\n git clone git@github.com:aekasitt/fastapi-csrf-protect.git\n cd fastapi-csrf-protect\n uv venv --python 3.9.6\n source .venv/bin/activate\n uv sync --dev\n ```\n</details>\n\n### Getting started\n\nTo contribute to the project, fork the repository and clone to your local device\nand install preferred testing dependency [pytest](https://github.com/pytest-dev/pytest)\nAlternatively, run the following command on your terminal to do so:\n\n```bash\nuv sync --dev\n```\n\nTesting can be done by the following command post-installation:\n\n```bash\nuv sync --dev --group tests\npytest\n```\n\n## Change-logs\n\n* **0.3.1** Adopt [Double Submit Cookie](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie)\n\n :construction: **BREAKING CHANGE**\n 0.3.0 -> 0.3.1: `generate_csrf` marked for deprecation\n\n* **0.3.2** Add `token_location` config (either `body` or `header`); Unset to prevent token reuse\n\n :construction: **BREAKING CHANGE**\n 0.3.1 -> 0.3.2: `validate_csrf` is now async\n\n* **0.3.5** Introduced Pydantic V2 related bug fixed in version 0.3.6; Affects `cookie_samesite`\n* **0.3.6** Fixed `cookie_samesite` validation bug introduced in previous version\n* **1.0.0** Remove deprecated `generate_csrf`, please use `generate_csrf_tokens` returning tuple\n* **1.0.1** Fix cookie unsetting when configuring lib with cookie `Secure` and / or `SameSite=None`\n* **1.0.2** Improve boolean handling for `LoadConfig`\n* **1.0.3** Failed experiement to integrate `mypyc` compilation due to dependency injection pattern\n* **1.0.4** Added flexible mode when `token_location` is omitted and multiple location checks\n\n :construction: **FAILED ROLLOUT**\n 1.0.3 -> 1.0.4: Rolled out with WIP code; immediately deleted version from PyPI\n\n* **1.0.5** Remove `@dataclass` leftover from failed experiment; Clarify failure reasons under tests\n* **1.0.6** Fix `Stream consumed` when submitted tokens via form data, `isinstance` consumes body\n\n### Run Examples\n\nTo run the provided examples, first you must install extra dependencies\n[granian](https://github.com/emmett-framework/granian) and [minijinja](https://github.com/mitsuhiko/minijinja/tree/main/minijinja-py)\nAlternatively, run the following command on your terminal to do so\n\n```bash\nuv sync --group=examples\n```\n\nRunning the example utilizing form submission\n\n```bash\ngranian --interface asgi examples.body:app\n```\n\nRunning the example utilizing headers via JavaScript\n\n```bash\ngranian --interface asgi examples.header:app\n```\n\n## License\n\nThis project is licensed under the terms of the MIT license.\n",
"bugtrack_url": null,
"license": null,
"summary": "Stateless implementation of Cross-Site Request Forgery (XSRF) Protection by using Double Submit Cookie mitigation pattern",
"version": "1.0.6",
"project_urls": null,
"split_keywords": [
"asynchronous",
" cross-site request forgery",
" csrf",
" fastapi",
" samesite",
" starlette",
" xsrf"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "ea99a2198eb3c42eff97a16c1454577c78ad212ee4d41437665abeac64bbb1a6",
"md5": "a7a4af83a7513222b5170210acbd4020",
"sha256": "07d772ffcdad36d567d52579d4b63995a8979c7f15c7d9f79c44ef3b206bba80"
},
"downloads": -1,
"filename": "fastapi_csrf_protect-1.0.6-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a7a4af83a7513222b5170210acbd4020",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 18046,
"upload_time": "2025-08-21T08:08:31",
"upload_time_iso_8601": "2025-08-21T08:08:31.553278Z",
"url": "https://files.pythonhosted.org/packages/ea/99/a2198eb3c42eff97a16c1454577c78ad212ee4d41437665abeac64bbb1a6/fastapi_csrf_protect-1.0.6-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "22d629c3185d8c91cc6beb9711fcfb48b54ecb6ae71a755960516709f9bb6231",
"md5": "8df94cc6891a166cd2600dbdb574a311",
"sha256": "467f1c4e8f3d855c2b8f1fce3b4ee481793a6d8e7e89f84d487d47dffbf453d2"
},
"downloads": -1,
"filename": "fastapi_csrf_protect-1.0.6.tar.gz",
"has_sig": false,
"md5_digest": "8df94cc6891a166cd2600dbdb574a311",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 203783,
"upload_time": "2025-08-21T08:08:33",
"upload_time_iso_8601": "2025-08-21T08:08:33.461096Z",
"url": "https://files.pythonhosted.org/packages/22/d6/29c3185d8c91cc6beb9711fcfb48b54ecb6ae71a755960516709f9bb6231/fastapi_csrf_protect-1.0.6.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-21 08:08:33",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "fastapi-csrf-protect"
}