Skip to content

Implement Modern CSRF Protection #98

@feliperalmeida

Description

@feliperalmeida

Code of Conduct

  • 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 implements CSRF protection. A very good resource about the implementation is available here.

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, caniuse).
The algorithm is described here.

Related Resources

https://web.dev/articles/fetch-metadata
https://words.filippo.io/csrf/
OWASP/CheatSheetSeries#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:

Implementation Suggestions

I already took a shot implementing this at the django-modern-csrf library. You can see the actual algorithm implementation here.

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

CSRF_TRUSTED_ORIGINS

The 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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Idea

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions