Skip to content

Commit

Permalink
Better csrf doc (#1314)
Browse files Browse the repository at this point in the history
* Better CSRF documentation

* Quickfix
  • Loading branch information
c4ffein authored Oct 9, 2024
1 parent 6d16ad2 commit 5f9dd3f
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 3 deletions.
5 changes: 4 additions & 1 deletion docs/docs/reference/csrf.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,12 @@ api = NinjaAPI(auth=django_auth)

#### Django `ensure_csrf_cookie` decorator
You can use the Django [ensure_csrf_cookie](https://docs.djangoproject.com/en/4.2/ref/csrf/#django.views.decorators.csrf.ensure_csrf_cookie) decorator on an unprotected route to make it include a `Set-Cookie` header for the CSRF token. Note that:

- The route decorator must be executed before (i.e. above) the [ensure_csrf_cookie](https://docs.djangoproject.com/en/4.2/ref/csrf/#django.views.decorators.csrf.ensure_csrf_cookie) decorator).
- You must `csrf_exempt` that route.
- The `ensure_csrf_cookie` decorator works only on a Django `HttpResponse` and not also on a dict like most Django Ninja decorators.
- The `ensure_csrf_cookie` decorator works only on a Django `HttpResponse` (and subclasses like `JsonResponse`) and not on a dict like most Django Ninja decorators.
- If you [set a Cookie based authentication (which includes `django_auth`) globally to your API](../guides/authentication.md), you'll have to specifically disable auth on that route (with `auth=None` in the route decorator) as Cookie based authentication would raise an Exception when applied to an unprotected route (for security reasons).

```python hl_lines="4"
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
Expand Down
52 changes: 50 additions & 2 deletions tests/test_csrf.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import re

from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie

from ninja import NinjaAPI
from ninja.security import APIKeyCookie, APIKeyHeader
from ninja.security import APIKeyCookie, APIKeyHeader, django_auth
from ninja.testing import TestClient as BaseTestClient


Expand All @@ -17,6 +18,9 @@ def _build_request(self, *args, **kwargs):

csrf_OFF = NinjaAPI(urls_namespace="csrf_OFF")
csrf_ON = NinjaAPI(urls_namespace="csrf_ON", csrf=True)
csrf_ON_with_django_auth = NinjaAPI(
urls_namespace="csrf_ON", csrf=True, auth=django_auth
)


@csrf_OFF.post("/post")
Expand Down Expand Up @@ -98,6 +102,50 @@ def test_view(request):
assert response.status_code == 200, response.content


def test_csrf_cookies_can_be_obtained():
@csrf_ON.get("/obtain_csrf_token_get")
@ensure_csrf_cookie
def obtain_csrf_token_get(request):
return JsonResponse(data={"success": True})

@csrf_ON.post("/obtain_csrf_token_post")
@ensure_csrf_cookie
@csrf_exempt
def obtain_csrf_token_post(request):
return JsonResponse(data={"success": True})

@csrf_ON_with_django_auth.get("/obtain_csrf_token_get", auth=None)
@ensure_csrf_cookie
def obtain_csrf_token_get_no_auth_route(request):
return JsonResponse(data={"success": True})

@csrf_ON_with_django_auth.post("/obtain_csrf_token_post", auth=None)
@ensure_csrf_cookie
@csrf_exempt
def obtain_csrf_token_post_no_auth_route(request):
return JsonResponse(data={"success": True})

client = TestClient(csrf_ON)
# can get csrf cookie through get
response = client.get("/obtain_csrf_token_get")
assert response.status_code == 200
assert len(response.cookies["csrftoken"].value) > 0
# can get csrf cookie through exempted post
response = client.post("/obtain_csrf_token_post")
assert response.status_code == 200
assert len(response.cookies["csrftoken"].value) > 0
# Now testing a route with disabled auth from a client with django_auth set globally also works
client = TestClient(csrf_ON_with_django_auth)
# can get csrf cookie through get on route with disabled auth
response = client.get("/obtain_csrf_token_get")
assert response.status_code == 200
assert len(response.cookies["csrftoken"].value) > 0
# can get csrf cookie through exempted post on route with disabled auth
response = client.post("/obtain_csrf_token_post")
assert response.status_code == 200
assert len(response.cookies["csrftoken"].value) > 0


def test_docs():
"Testing that docs are initializing csrf headers correctly"

Expand Down

0 comments on commit 5f9dd3f

Please sign in to comment.