Typorithm · medium

Test cookie sessions with CSRF enforcement enabled

pythondjangodrfindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Test cookie sessions with CSRF enforcement enabled

0%

Concept and contract

Use the real session middleware and token instead of bypassing the browser security boundary.

A browser can attach a session cookie automatically when making a request. An unsafe authenticated operation therefore needs CSRF protection so another origin cannot simply cause a state-changing request with that ambient credential. Session identity and request integrity are different checks.

The fixture creates a real owned session using APIClient.force_login, obtains a CSRF cookie from a safe seed route and submits the token in X-CSRFToken. The client explicitly enables CSRF checks. No real browser account, external site or production write is involved.

Mechanism and worked example

Django SessionMiddleware and AuthenticationMiddleware resolve the session user.

Django SessionMiddleware and AuthenticationMiddleware resolve the session user. DRF SessionAuthentication enforces CSRF for the authenticated unsafe request. A valid cookie/token pair reaches the protected POST operation; omitting the header produces a permission failure.

The seed route uses ensure_csrf_cookie so token acquisition is explicit. The helper does not force_authenticate the request, because doing so would bypass the session mechanism this topic needs to exercise. A logged-out request still fails the declared IsAuthenticated policy.

Complete worked example

python

from drf_lab import setup
setup()

from django.test import override_settings
from django.urls import path, clear_url_caches
from django.views.decorators.csrf import ensure_csrf_cookie
from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import api_view
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.test import APIClient
from rest_framework.views import APIView
from drf_lab import reset_fixture

@ensure_csrf_cookie
@api_view(["GET"])
def seed(request):
    return Response({"ready": True})
class Change(APIView):
    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated]
    def post(self, request):
        return Response({"by": request.user.username, "accepted": True})

urlpatterns = [path("csrf/", seed), path("change/", Change.as_view())]

def inspect_example(payload):
    alice, _, _ = reset_fixture()
    client = APIClient(enforce_csrf_checks=True)
    if payload.get("login", True):
        client.force_login(alice)
    with override_settings(ROOT_URLCONF=__name__):
        clear_url_caches()
        client.get("/csrf/")
        headers = {"HTTP_X_CSRFTOKEN": client.cookies["csrftoken"].value} if payload.get("token") else {}
        response = getattr(client, payload.get("method", "post"))("/change/", {}, format="json", **headers)
    clear_url_caches()
    return {"status": response.status_code, "body":
            {"error": response.data["detail"].code} if response.status_code >= 400 else response.json()}
Reasoning
Django SessionMiddleware and AuthenticationMiddleware resolve the session user. DRF SessionAuthentication enforces CSRF for the authenticated unsafe request. A valid cookie/token pair reaches the protected POST operation; omitting the header produces a permission failure. The seed route uses ensure_csrf_cookie so token acquisition is explicit. The helper does not force_authenticate the request, because doing so would bypass the session mechanism this topic needs to exercise. A logged-out request still fails the declared IsAuthenticated policy.

Focused decision: context is in the complete example

python

client = APIClient(enforce_csrf_checks=True)
client.force_login(alice)
client.get("/csrf/")
token = client.cookies["csrftoken"].value
response = client.post("/change/", {}, format="json", HTTP_X_CSRFTOKEN=token)
Focused decision
Reconstruct this focused decision after the warm-up. The complete imports, model or application, and test client remain in the worked example. Typing records practice; only the downloadable computer lab executes Python.

Counterexamples and common mistakes

The counterexample uses a test client with CSRF enforcement disabled.

The counterexample uses a test client with CSRF enforcement disabled. The missing-token request can then appear successful, producing a falsely reassuring test. Default testing conveniences must not be mistaken for a demonstrated security property.

Do not disable CSRF on a cookie-authenticated write merely because a local test failed. Check token acquisition, cookie scope, origin policy and deployment HTTPS settings. A stateless Authorization-header design has a different credential model, but it still needs an explicit security review rather than a blanket exemption claim.

Controlled mistake: observe the actual wrong behavior

python

from drf_lab import setup
setup()

from django.test import override_settings
from django.urls import path, clear_url_caches
from django.views.decorators.csrf import ensure_csrf_cookie
from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import api_view
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.test import APIClient
from rest_framework.views import APIView
from drf_lab import reset_fixture

@ensure_csrf_cookie
@api_view(["GET"])
def seed(request):
    return Response({"ready": True})
class Change(APIView):
    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated]
    def post(self, request):
        return Response({"by": request.user.username, "accepted": True})

urlpatterns = [path("csrf/", seed), path("change/", Change.as_view())]

def inspect_example(payload):
    alice, _, _ = reset_fixture()
    client = APIClient(enforce_csrf_checks=False)
    if payload.get("login", True):
        client.force_login(alice)
    with override_settings(ROOT_URLCONF=__name__):
        clear_url_caches()
        client.get("/csrf/")
        headers = {"HTTP_X_CSRFTOKEN": client.cookies["csrftoken"].value} if payload.get("token") else {}
        response = getattr(client, payload.get("method", "post"))("/change/", {}, format="json", **headers)
    clear_url_caches()
    return {"status": response.status_code, "body":
            {"error": response.data["detail"].code} if response.status_code >= 400 else response.json()}
Reasoning
The counterexample uses a test client with CSRF enforcement disabled. The missing-token request can then appear successful, producing a falsely reassuring test. Default testing conveniences must not be mistaken for a demonstrated security property. Do not disable CSRF on a cookie-authenticated write merely because a local test failed. Check token acquisition, cookie scope, origin policy and deployment HTTPS settings. A stateless Authorization-header design has a different credential model, but it still needs an explicit security review rather than a blanket exemption claim.

Independent practice

Allow PUT to use the same protected operation as POST.

Allow PUT to use the same protected operation as POST. Add a small method alias while preserving session authentication, CSRF checks and the existing permission policy. This extends the method contract without duplicating or bypassing the guarded handler.

Test PUT with a valid token, PUT without it and an ordinary valid POST. The accepted paths should return the same small public result. Keep the client enforcement flag and real session setup intact; changing only expected statuses would leave the actual boundary untested.

Step 1: Support PUT through the same protected handler and retain denial when its CSRF token is absent. Step 2: Retain the working imports and application structure. Change the stated policy, run the supplied practice cases, then explain the newly accepted or rejected boundary.

Practice starter: solve before reading the solution

python

from drf_lab import setup
setup()

from django.test import override_settings
from django.urls import path, clear_url_caches
from django.views.decorators.csrf import ensure_csrf_cookie
from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import api_view
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.test import APIClient
from rest_framework.views import APIView
from drf_lab import reset_fixture

@ensure_csrf_cookie
@api_view(["GET"])
def seed(request):
    return Response({"ready": True})
class Change(APIView):
    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated]
    def post(self, request):
        return Response({"by": request.user.username, "accepted": True})

urlpatterns = [path("csrf/", seed), path("change/", Change.as_view())]

def inspect_example(payload):
    alice, _, _ = reset_fixture()
    client = APIClient(enforce_csrf_checks=True)
    if payload.get("login", True):
        client.force_login(alice)
    with override_settings(ROOT_URLCONF=__name__):
        clear_url_caches()
        client.get("/csrf/")
        headers = {"HTTP_X_CSRFTOKEN": client.cookies["csrftoken"].value} if payload.get("token") else {}
        response = getattr(client, payload.get("method", "post"))("/change/", {}, format="json", **headers)
    clear_url_caches()
    return {"status": response.status_code, "body":
            {"error": response.data["detail"].code} if response.status_code >= 400 else response.json()}

# Independent task: Support PUT through the same protected handler and retain denial when its CSRF token is absent.
Reasoning
Support PUT through the same protected handler and retain denial when its CSRF token is absent.
  • Hint 1: Find the one field or branch that owns this policy.
  • Hint 2: Compare the first changed acceptance case with the retained baseline.

Solution, tests and trade-offs

The solution adds put=post on the view class.

The solution adds put=post on the view class. DRF still applies authentication and permission checks before dispatching the selected handler. The missing-token PUT remains denied, demonstrating that the alias did not create an unprotected side path.

A production write also needs object ownership, validation and a transaction. A successful CSRF check does not make arbitrary request fields safe or authorize a record. Cookies should use suitable secure, HttpOnly and SameSite settings for the deployment, and those browser behaviors need their own integration tests.

Practice solution and acceptance checks

python

from drf_lab import setup
setup()

from django.test import override_settings
from django.urls import path, clear_url_caches
from django.views.decorators.csrf import ensure_csrf_cookie
from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import api_view
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.test import APIClient
from rest_framework.views import APIView
from drf_lab import reset_fixture

@ensure_csrf_cookie
@api_view(["GET"])
def seed(request):
    return Response({"ready": True})
class Change(APIView):
    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated]
    def post(self, request):
        return Response({"by": request.user.username, "accepted": True})
    put = post

urlpatterns = [path("csrf/", seed), path("change/", Change.as_view())]

def inspect_example(payload):
    alice, _, _ = reset_fixture()
    client = APIClient(enforce_csrf_checks=True)
    if payload.get("login", True):
        client.force_login(alice)
    with override_settings(ROOT_URLCONF=__name__):
        clear_url_caches()
        client.get("/csrf/")
        headers = {"HTTP_X_CSRFTOKEN": client.cookies["csrftoken"].value} if payload.get("token") else {}
        response = getattr(client, payload.get("method", "post"))("/change/", {}, format="json", **headers)
    clear_url_caches()
    return {"status": response.status_code, "body":
            {"error": response.data["detail"].code} if response.status_code >= 400 else response.json()}
Reasoning
The solution adds put=post on the view class. DRF still applies authentication and permission checks before dispatching the selected handler. The missing-token PUT remains denied, demonstrating that the alias did not create an unprotected side path. A production write also needs object ownership, validation and a transaction. A successful CSRF check does not make arbitrary request fields safe or authorize a record. Cookies should use suitable secure, HttpOnly and SameSite settings for the deployment, and those browser behaviors need their own integration tests.

Interview reasoning and next step

Explain why force_login is appropriate for establishing an owned session while force_authenticate would skip the mechanism under test.

Explain why force_login is appropriate for establishing an owned session while force_authenticate would skip the mechanism under test. Then identify the deliberately disabled test-client setting that creates the false positive.

Next, combine explicit filters, search and ordering. Security boundaries must be preserved when a caller changes the shape of a collection query; a flexible filter interface should not silently widen ownership or expose private fields.

Optional stretch — after the core task works: Add one independently reasoned boundary case and explain its expected value before running it.

Computer practice: https://applydjinn.com/typorithm/drf-lab-20261105.zip. Install pinned dependencies in an isolated Python environment. Run verify.py, then change the working exercise baseline and run --practice drf-session-csrf. The real DRF clients, serializers, schemas and SQLite transactions use owned fixtures only. No production database, feed, account or paid service is used. This lab executes Python; the PC typing trainer and native reading progress do not execute it.

Use the real session middleware and token instead of bypassing the browser security boundary.