Skip to content

Scope and conditions for disabling CSRF on /api/ may be too broad #31

Description

@Polqt

🚨 suggestion (security): Scope and conditions for disabling CSRF on /api/ may be too broad

This disables CSRF for all /api/ requests, including unsafe methods using session/cookie auth, which is a significant security risk. Please scope this more narrowly (e.g., only for specific views or subpaths that use token/JWT auth or other non-cookie mechanisms) and avoid relying on a hard-coded /api/ prefix that may change or not cover all API routes.

Suggested implementation:

from django.conf import settings


UNSAFE_METHODS = {"POST", "PUT", "PATCH", "DELETE"}
CSRF_EXEMPT_API_PREFIXES = tuple(
    getattr(settings, "CSRF_EXEMPT_API_PREFIXES", ())
)


def _is_exempt_api_path(path: str) -> bool:
    """
    Returns True if the given path is configured as CSRF-exempt for API usage.

    Paths are configured via settings.CSRF_EXEMPT_API_PREFIXES as a tuple/list
    of path prefixes (e.g. ["/api/auth/", "/api/webhooks/"]).
    """
    return any(path.startswith(prefix) for prefix in CSRF_EXEMPT_API_PREFIXES)


def _has_non_cookie_auth(request) -> bool:
    """
    Heuristically detect token/JWT/API-key style auth so we only disable CSRF
    where cookie-based session auth is not expected.
    """
    auth_header = request.META.get("HTTP_AUTHORIZATION", "")
    api_key_header = request.META.get("HTTP_X_API_KEY", "")

    if auth_header:
        # Common patterns: "Bearer <token>", "Token <token>", "JWT <token>", etc.
        return True

    if api_key_header:
        return True

    return False


def disable_csrf(get_response):
    """
    Middleware that selectively disables CSRF checks for API endpoints that:
    - Match configured CSRF_EXEMPT_API_PREFIXES, and
    - Use non-cookie authentication mechanisms (e.g. Authorization/API key), and
    - Use an unsafe HTTP method.

    This avoids disabling CSRF protection broadly for all `/api/` requests.
    """
    def middleware(request):
        if (
            request.method in UNSAFE_METHODS
            and _is_exempt_api_path(request.path)
            and _has_non_cookie_auth(request)
        ):
            setattr(request, "_dont_enforce_csrf_checks", True)

        return get_response(request)

    return middleware

To fully adopt this change, configure the specific API subpaths that should be CSRF-exempt in your Django settings, for example:

  • In settings.py (or appropriate settings module):
CSRF_EXEMPT_API_PREFIXES = [
    "/api/auth/",      # e.g. JWT login/refresh endpoints
    "/api/webhooks/",  # e.g. third-party webhooks using token signatures
]

Review and adjust CSRF_EXEMPT_API_PREFIXES so only endpoints using token/JWT/non-cookie auth are listed, and avoid including a broad /api/ prefix.

Originally posted by @sourcery-ai[bot] in #30 (comment)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions