django-modern-csrf


Namedjango-modern-csrf JSON
Version 1.0.1 PyPI version JSON
download
home_pageNone
SummaryModern CSRF protection for Django using Fetch metadata headers.
upload_time2025-10-28 13:33:48
maintainerNone
docs_urlNone
authorFelipe R. de Almeida
requires_python>=3.10
licenseNone
keywords django security csrf
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # django-modern-csrf

[![PyPI version](https://badge.fury.io/py/django-modern-csrf.svg)](https://badge.fury.io/py/django-modern-csrf)
[![PyPI Supported Python Versions](https://img.shields.io/pypi/pyversions/django-modern-csrf.svg)](https://pypi.python.org/pypi/django-modern-csrf/)
[![tests](https://github.com/feliperalmeida/django-modern-csrf/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/feliperalmeida/django-modern-csrf/actions/workflows/main.yml)
[![codecov](https://codecov.io/github/feliperalmeida/django-modern-csrf/graph/badge.svg?token=F8O6BPUYPH)](https://codecov.io/github/feliperalmeida/django-modern-csrf)

Django modern CSRF protection using **Fetch metadata** request headers, without tokens, cookies or custom headers. No
more CSRF token errors, `csrf_token` in your templates or configuring frontend clients to deal with `X-CSRFToken`.

## Rationale

Django's default [CSRF protection](https://docs.djangoproject.com/en/5.2/ref/csrf/) relies on tokens and cookies. While
this works well and is secure, there are more
modern ways to protect against CSRF attacks, without requiring to submit CSRF tokens to the server via forms, cookies or
custom headers.
[Fetch metadata request headers](https://developer.mozilla.org/en-US/docs/Glossary/Fetch_metadata_request_header)
provide a way to protect against CSRF attacks without requiring token verifications. The `Sec-Fetch-Site` in particular,
tells the server whether the request is same origin, cross-origin, same site or user initiated. With that information,
the server can decide whether to allow the request or not.

To learn more about Fetch metadata request headers and how they can be used for web security, check out this
[article](https://web.dev/fetch-metadata/) by Google.

According to Can I Use, the `Sec-Fetch-Site` header is supported
by [97.63%](https://caniuse.com/mdn-http_headers_sec-fetch-site) and `Origin`
by [99.83%](https://caniuse.com/mdn-http_headers_origin) of all tracked browsers.

## Installation

This package is designed to require minimal changes to your existing Django project.

1. Install the package:

```bash
pip install django-modern-csrf
```

2. Add `modern_csrf` to your `INSTALLED_APPS`:

```python
# settings.py

INSTALLED_APPS = [
    ...
    "modern_csrf",
    ...
]
```

3. Replace the default CSRF middleware with the new one (in the same position):

```python
# settings.py

# Old
MIDDLEWARE = [
    ...
    "django.middleware.csrf.CsrfViewMiddleware",
    ...
]

# New
MIDDLEWARE = [
    ...
    "modern_csrf.middleware.ModernCsrfViewMiddleware",
    ...
]
```

4. That should be it for most projects. However, if you are using the `@csrf_protect` decorator, you will need to
   replace
   it with this package's `@csrf_protect`.

```python
# Old
from django.views.decorators.csrf import csrf_protect

# Replace with
from modern_csrf.decorators import csrf_protect
```

5. That's it! You can remove all references to `{% csrf_token %}` or `{{ csrf_token }}` from your templates. You can
   also
   remove any code in your JavaScript clients that sets the `X-CSRFToken` header, as there are no more token checks in
   place. Enjoy your tokenless CSRF protection!

## Implementation

The implementation for this package is based on the Go standard library's protection against CSRF attacks. The
implementation can be seen [here](https://cs.opensource.google/go/go/+/refs/tags/go1.25rc2:src/net/http/csrf.go;l=122),
along with the [research by the author](https://words.filippo.io/csrf/) who implemented it.

The `ModernCsrfViewMiddleware` is a drop-in replacement for the default `CsrfViewMiddleware` that uses the
`Sec-Fetch-Site` header to determine whether to allow the request or not. Below is a description of how it works:

1. Skip CSRF protection if the view is explicitly marked as CSRF-exempt.

2. Allow all GET, HEAD, OPTIONS, or TRACE requests.

   These are safe methods as defined by RFC 9110, and are assumed not to change state.

3. If the `Sec-Fetch-Site` header is present:
    - if its value is `same-origin` or `none`, allow the request;
    - otherwise, check if the `Origin` header is verified against trusted origins (Django's `CSRF_TRUSTED_ORIGINS`);
    - if the Origin is verified, allow the request;
    - otherwise, reject the request.

   This leverages modern browser security headers to detect cross-origin requests. The `Sec-Fetch-Site` header is
   automatically sent by modern browsers and provides reliable origin information.

4. If the `Origin` header is present but doesn't match any trusted origin, reject the request.

   This catches cross-origin requests from browsers that don't support `Sec-Fetch-Site` but do send `Origin` headers.

5. If neither the `Sec-Fetch-Site` nor the `Origin` headers triggered a rejection, allow the request.

   This is the default fallback for requests that don't present obvious cross-origin indicators. Those requests are most
   likely from command line tools or other non-browser clients.

Origin verification checks three conditions:

- (1) the Origin matches the current request's origin (scheme + host + port if present),
- (2) the Origin is in the `CSRF_TRUSTED_ORIGINS` allow-list as an exact match,
- (3) the Origin's domain is a subdomain of an allowed wildcard entry in `CSRF_TRUSTED_ORIGINS`.

This approach prioritizes the Sec-Fetch-Site header, which provides the most reliable protection for modern browsers,
while maintaining backward compatibility through
Origin header validation. The algorithm has no false negatives in browsers that support Sec-Fetch-Site (all major
browsers since 2020), and degrades gracefully for older browsers.

## Acknowledgements

Thanks to [Filippo Valsorda](https://github.com/FiloSottile) for the research and implementation of Go's
`CrossOriginProtection` which this package is based on.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "django-modern-csrf",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "django, security, csrf",
    "author": "Felipe R. de Almeida",
    "author_email": "Felipe R. de Almeida <felipe@owasp.org>",
    "download_url": "https://files.pythonhosted.org/packages/84/f6/bb760a2b01f77f3b4004578f69787267e113c8bae19ff3af647724df852f/django_modern_csrf-1.0.1.tar.gz",
    "platform": null,
    "description": "# django-modern-csrf\n\n[![PyPI version](https://badge.fury.io/py/django-modern-csrf.svg)](https://badge.fury.io/py/django-modern-csrf)\n[![PyPI Supported Python Versions](https://img.shields.io/pypi/pyversions/django-modern-csrf.svg)](https://pypi.python.org/pypi/django-modern-csrf/)\n[![tests](https://github.com/feliperalmeida/django-modern-csrf/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/feliperalmeida/django-modern-csrf/actions/workflows/main.yml)\n[![codecov](https://codecov.io/github/feliperalmeida/django-modern-csrf/graph/badge.svg?token=F8O6BPUYPH)](https://codecov.io/github/feliperalmeida/django-modern-csrf)\n\nDjango modern CSRF protection using **Fetch metadata** request headers, without tokens, cookies or custom headers. No\nmore CSRF token errors, `csrf_token` in your templates or configuring frontend clients to deal with `X-CSRFToken`.\n\n## Rationale\n\nDjango's default [CSRF protection](https://docs.djangoproject.com/en/5.2/ref/csrf/) relies on tokens and cookies. While\nthis works well and is secure, there are more\nmodern ways to protect against CSRF attacks, without requiring to submit CSRF tokens to the server via forms, cookies or\ncustom headers.\n[Fetch metadata request headers](https://developer.mozilla.org/en-US/docs/Glossary/Fetch_metadata_request_header)\nprovide a way to protect against CSRF attacks without requiring token verifications. The `Sec-Fetch-Site` in particular,\ntells the server whether the request is same origin, cross-origin, same site or user initiated. With that information,\nthe server can decide whether to allow the request or not.\n\nTo learn more about Fetch metadata request headers and how they can be used for web security, check out this\n[article](https://web.dev/fetch-metadata/) by Google.\n\nAccording to Can I Use, the `Sec-Fetch-Site` header is supported\nby [97.63%](https://caniuse.com/mdn-http_headers_sec-fetch-site) and `Origin`\nby [99.83%](https://caniuse.com/mdn-http_headers_origin) of all tracked browsers.\n\n## Installation\n\nThis package is designed to require minimal changes to your existing Django project.\n\n1. Install the package:\n\n```bash\npip install django-modern-csrf\n```\n\n2. Add `modern_csrf` to your `INSTALLED_APPS`:\n\n```python\n# settings.py\n\nINSTALLED_APPS = [\n    ...\n    \"modern_csrf\",\n    ...\n]\n```\n\n3. Replace the default CSRF middleware with the new one (in the same position):\n\n```python\n# settings.py\n\n# Old\nMIDDLEWARE = [\n    ...\n    \"django.middleware.csrf.CsrfViewMiddleware\",\n    ...\n]\n\n# New\nMIDDLEWARE = [\n    ...\n    \"modern_csrf.middleware.ModernCsrfViewMiddleware\",\n    ...\n]\n```\n\n4. That should be it for most projects. However, if you are using the `@csrf_protect` decorator, you will need to\n   replace\n   it with this package's `@csrf_protect`.\n\n```python\n# Old\nfrom django.views.decorators.csrf import csrf_protect\n\n# Replace with\nfrom modern_csrf.decorators import csrf_protect\n```\n\n5. That's it! You can remove all references to `{% csrf_token %}` or `{{ csrf_token }}` from your templates. You can\n   also\n   remove any code in your JavaScript clients that sets the `X-CSRFToken` header, as there are no more token checks in\n   place. Enjoy your tokenless CSRF protection!\n\n## Implementation\n\nThe implementation for this package is based on the Go standard library's protection against CSRF attacks. The\nimplementation can be seen [here](https://cs.opensource.google/go/go/+/refs/tags/go1.25rc2:src/net/http/csrf.go;l=122),\nalong with the [research by the author](https://words.filippo.io/csrf/) who implemented it.\n\nThe `ModernCsrfViewMiddleware` is a drop-in replacement for the default `CsrfViewMiddleware` that uses the\n`Sec-Fetch-Site` header to determine whether to allow the request or not. Below is a description of how it works:\n\n1. Skip CSRF protection if the view is explicitly marked as CSRF-exempt.\n\n2. Allow all GET, HEAD, OPTIONS, or TRACE requests.\n\n   These are safe methods as defined by RFC 9110, and are assumed not to change state.\n\n3. If the `Sec-Fetch-Site` header is present:\n    - if its value is `same-origin` or `none`, allow the request;\n    - otherwise, check if the `Origin` header is verified against trusted origins (Django's `CSRF_TRUSTED_ORIGINS`);\n    - if the Origin is verified, allow the request;\n    - otherwise, reject the request.\n\n   This leverages modern browser security headers to detect cross-origin requests. The `Sec-Fetch-Site` header is\n   automatically sent by modern browsers and provides reliable origin information.\n\n4. If the `Origin` header is present but doesn't match any trusted origin, reject the request.\n\n   This catches cross-origin requests from browsers that don't support `Sec-Fetch-Site` but do send `Origin` headers.\n\n5. If neither the `Sec-Fetch-Site` nor the `Origin` headers triggered a rejection, allow the request.\n\n   This is the default fallback for requests that don't present obvious cross-origin indicators. Those requests are most\n   likely from command line tools or other non-browser clients.\n\nOrigin verification checks three conditions:\n\n- (1) the Origin matches the current request's origin (scheme + host + port if present),\n- (2) the Origin is in the `CSRF_TRUSTED_ORIGINS` allow-list as an exact match,\n- (3) the Origin's domain is a subdomain of an allowed wildcard entry in `CSRF_TRUSTED_ORIGINS`.\n\nThis approach prioritizes the Sec-Fetch-Site header, which provides the most reliable protection for modern browsers,\nwhile maintaining backward compatibility through\nOrigin header validation. The algorithm has no false negatives in browsers that support Sec-Fetch-Site (all major\nbrowsers since 2020), and degrades gracefully for older browsers.\n\n## Acknowledgements\n\nThanks to [Filippo Valsorda](https://github.com/FiloSottile) for the research and implementation of Go's\n`CrossOriginProtection` which this package is based on.\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Modern CSRF protection for Django using Fetch metadata headers.",
    "version": "1.0.1",
    "project_urls": null,
    "split_keywords": [
        "django",
        " security",
        " csrf"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "6f3e7aedb5a679da43ac01e421956b45bf97d3d7f48db30c55f093c0a28ddb64",
                "md5": "04c282d02c01a3b1301f00669d1423a1",
                "sha256": "fce69521d2a832a6f3243fc5985cbd61a925fc782e7308e0f3b9ad0b4d219e11"
            },
            "downloads": -1,
            "filename": "django_modern_csrf-1.0.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "04c282d02c01a3b1301f00669d1423a1",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 6705,
            "upload_time": "2025-10-28T13:33:47",
            "upload_time_iso_8601": "2025-10-28T13:33:47.292800Z",
            "url": "https://files.pythonhosted.org/packages/6f/3e/7aedb5a679da43ac01e421956b45bf97d3d7f48db30c55f093c0a28ddb64/django_modern_csrf-1.0.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "84f6bb760a2b01f77f3b4004578f69787267e113c8bae19ff3af647724df852f",
                "md5": "041cfaea05b374de5c19c20e64d3b3ab",
                "sha256": "6d13bf8eb887457f676d6f70987e9670353ef495e81d695d27c26e71e727a7f7"
            },
            "downloads": -1,
            "filename": "django_modern_csrf-1.0.1.tar.gz",
            "has_sig": false,
            "md5_digest": "041cfaea05b374de5c19c20e64d3b3ab",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 5520,
            "upload_time": "2025-10-28T13:33:48",
            "upload_time_iso_8601": "2025-10-28T13:33:48.196447Z",
            "url": "https://files.pythonhosted.org/packages/84/f6/bb760a2b01f77f3b4004578f69787267e113c8bae19ff3af647724df852f/django_modern_csrf-1.0.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-28 13:33:48",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "django-modern-csrf"
}
        
Elapsed time: 2.12425s