django / django/new-features

Implement Modern CSRF Protection

Open
#98 12 comments 59 reactions 0 assignees View on GitHub
Django Core Middleware Templates
Dominant language
No language data
Stars
188
Forks
7
PR merge metrics
No merged PRs in 30d

Description

### Code of Conduct

- [x] I agree to follow Django's Code of Conduct

### Feature Description

Django's CSRF protection is based on tokens. It works well but requires some effort from the developer to make it happen - passing around CSRF tokens in templates, form fields and HTTP headers.

Today, a more modern implementation can be achieved using Fetch Metadata and Origin request headers. Those headers are automatically sent by the browser. This is how the [Go standard library](https://cs.opensource.google/go/go/+/refs/tags/go1.25.4:src/net/http/csrf.go;l=130) implements CSRF protection. A very good resource about the implementation is available [here](https://words.filippo.io/csrf/).

This proposal is to implement the Go's stdlib CSRF protection approach on Django. It uses the `Sec-Fetch-Site` header with `Origin` as a fallback. Regarding browser compatibility, this covers all versions of Chrome and Safari ever, Firefox going back to 2019, Edge going back to 2018, and even IE 11. ([source](https://github.com/OWASP/CheatSheetSeries/issues/1803#issuecomment-3508981858), [caniuse](https://caniuse.com/mdn-http_headers_origin)).
The algorithm is described [here](https://words.filippo.io/csrf/#protecting-against-csrf-in-2025).

#### Related Resources
https://web.dev/articles/fetch-metadata
https://words.filippo.io/csrf/
https://github.com/OWASP/CheatSheetSeries/issues/1803

### Problem

- Modernize Django's CSRF protection mechanism
- Reduces developer friction, as there's no need to implement token logic on the front-end
- Simpler logic on the backend

### Request or proposal

proposal

### Additional Details

Requests for something like this:
- https://chaos.social/@apollo13/115026470230309000
- https://code.djangoproject.com/ticket/31823
- https://discord.com/channels/856567261900832808/1432815378861527151 (some people on this thread)
- https://www.reddit.com/r/django/comments/1oihb4l/comment/nm1xdhe/

### Implementation Suggestions

I already took a shot implementing this at the [django-modern-csrf](https://github.com/feliperalmeida/django-modern-csrf) library. You can see the actual algorithm implementation [here](https://github.com/feliperalmeida/django-modern-csrf/blob/fbf2182e72c031e71d73e0c5e7e89a333b6c5340/modern_csrf/middleware.py#L96).

There are some considerations to discuss before implementing this in Django. The ones I can think of:

#### `CSRF_TRUSTED_ORIGINS`
The [Sec-Fetch-Site](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site) header uses different values for `same-site` and `same-origin`. This conflicts with the current approach for trusted origins. For example, if `CSRF_TRUSTED_ORIGINS` is set to `https://*.example.com`, it won't be possible to deny `same-site` requests.

Probably a new setting will need to be created so the developer can pick the appropriate allowed values for the `Sec-Fetch-Site` header. Something like:

`CSRF_SEC_FETCH_ALLOWED`: Defines values allowed on the `Sec-Fetch-Site` header. **Default**: `['same-origin', 'none']`.
_Suggestions for the setting name are welcome_

So if the developer wants to allow `*.example.com`, both settings would have to be set accordingly (`_TRUSTED_ORIGINS` and the new one).

#### HTTPS
The `Sec-Fetch-Site` header is only sent to trustworthy origins, i.e. HTTPS and localhost. As the Origin header serves as a fallback, it shouldn't be a concern - however it's good to have it documented.

#### `@csrf_protect`
The `csrf_protect` decorator currently wraps the `CsrfViewMiddleware` on the view. If the new approach is implemented as a separate middleware this will have to be updated.

#### Let the user choose the best protection strategy - and pick a default
I think developers should be able to pick the right protection strategy for their needs. Even though the modern approach will probably be enough for most users, there will be cases where the token approach is still preferred.

This could be either by having a separate middleware so users can pick the appropriate one on the `MIDDLEWARE` setting, or a new config can be introduced (something like `CSRF_PROTECTION_STRATEGY` = `"modern"` or `"legacy"`) and then the appropriate algorithm is applied on the middleware.

#### Migration
How to migrate current codebases to the new approach? One way would be would be to introduce the new algorithm but developers would have to manually change it if they want. For new projects (via `startproject`) the modern approach could be the default?

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.