Menu
CoddyTech

CSRF y API

Parte de la sección API JSON del Journey de Django de Coddy. Lección 6 de 19.

Un programa real que envía POST /api/tasks/ recibe 403 Prohibido. La protección CSRF de Django exige que cada POST lleve un token secreto de una de tus propias páginas, para que otro sitio web no pueda hacer que el navegador de un visitante envíe tus formularios. Un programa no tiene esa página ni ese token.

@csrf_exempt, de django.views.decorators.csrf, desactiva la comprobación para una vista:

@csrf_exempt
def webhook(request):
    ...

Eso implica renunciar a cierta protección: cualquier sitio web puede hacer que el navegador de un visitante envíe este POST. Está bien para una vista que no depende de quién haya iniciado sesión, como la lista de tareas. Una API que actúa como el usuario que ha iniciado sesión mantiene CSRF activado, y el script de su página envía el token en un encabezado X-CSRFToken.

challenge icon

Desafío

Intermedio

Permite que los programas creen tareas sin un token CSRF:

  • POST /api/tasks/ funciona sin token.
  • Solo task_list está exenta.

Pulsa Run y después escribe esto en la pestaña Shell. Antes del cambio imprime 403; después, 201: from django.test import Client; print(Client(enforce_csrf_checks=True).post("/api/tasks/", {"title": "Pay rent"}, content_type="application/json").status_code)

Pruébalo tú mismo

import json

from django.http import JsonResponse
from django.shortcuts import render

from .models import Task


def task_to_dict(task):
    return {
        "id": task.id,
        "title": task.title,
        "done": task.done,
        "priority": task.priority,
    }


def home(request):
    return render(request, "tasks/home.html", {"tasks": Task.objects.all()})


def stats(request):
    data = {
        "total": Task.objects.count(),
        "done": Task.objects.filter(done=True).count(),
    }
    return JsonResponse(data)


def task_list(request):
    if request.method == "POST":
        data = json.loads(request.body)
        task = Task.objects.create(
            title=data["title"],
            priority=data.get("priority", "normal"),
        )
        return JsonResponse(task_to_dict(task), status=201)
    tasks = Task.objects.all()
    done = request.GET.get("done")
    if done == "true":
        tasks = tasks.filter(done=True)
    elif done == "false":
        tasks = tasks.filter(done=False)
    return JsonResponse([task_to_dict(task) for task in tasks], safe=False)


def task_detail(request, pk):
    task = Task.objects.filter(pk=pk).first()
    if task is None:
        return JsonResponse({"error": "Task not found"}, status=404)
    return JsonResponse(task_to_dict(task))

Todas las lecciones de API JSON

Practica por tu cuenta: Playground de Django