{"id":9337,"library":"starlette-csrf","title":"Starlette CSRF Middleware","description":"Starlette-CSRF is an active Python middleware designed for Starlette and FastAPI applications to mitigate Cross-Site Request Forgery (CSRF) attacks. It implements the Double Submit Cookie technique, providing protection by requiring a secret value to be sent in both a cookie and a request header for unsafe HTTP methods. The library is currently at version 3.0.0 and maintains a steady release cadence, with the latest major update focusing on Python version compatibility and argument handling.","status":"active","version":"3.0.0","language":"en","source_language":"en","source_url":"https://github.com/frankie567/starlette-csrf","tags":["starlette","fastapi","csrf","security","middleware","asgi"],"install":[{"cmd":"pip install starlette-csrf","lang":"bash","label":"Install latest version"}],"dependencies":[{"reason":"Core framework dependency. Compatible with `starlette >=0.14.2` historically, but generally works with recent versions.","package":"starlette","optional":false},{"reason":"Used for cryptographic signing of the CSRF token. Compatible with `itsdangerous >=2.0.1,<3.0.0` historically, but generally works with recent versions.","package":"itsdangerous","optional":false}],"imports":[{"symbol":"CSRFMiddleware","correct":"from starlette_csrf import CSRFMiddleware"}],"quickstart":{"code":"import os\nimport uvicorn\nfrom fastapi import FastAPI, Request, Response, Form\nfrom starlette.middleware import Middleware\nfrom starlette.routing import Route\nfrom starlette.responses import HTMLResponse\nfrom starlette_csrf import CSRFMiddleware\n\n# Ensure you have a strong secret key\nSECRET_KEY = os.environ.get('STARLETTE_CSRF_SECRET', 'a-very-secret-key-that-you-should-change-in-production')\n\napp = FastAPI(\n    middleware=[\n        Middleware(CSRFMiddleware, secret=SECRET_KEY)\n    ]\n)\n\n@app.get(\"/\", response_class=HTMLResponse)\nasync def read_root(request: Request):\n    # The CSRF token is automatically set in a cookie on GET requests\n    # and can be accessed via request.state.csrftoken for templates.\n    # In a real application, you'd embed this in your HTML forms.\n    token = request.state.csrftoken if hasattr(request.state, 'csrftoken') else 'No token (GET request initial load)'\n    return f'''\n    <html>\n        <head>\n            <title>CSRF Test</title>\n        </head>\n        <body>\n            <h1>Welcome!</h1>\n            <p>CSRF Token in state (for display only): {token}</p>\n            <form method=\"post\" action=\"/submit\">\n                <input type=\"text\" name=\"item\" placeholder=\"Enter item\">\n                <!-- In a real frontend, you'd get this from a cookie or initial GET response -->\n                <input type=\"hidden\" name=\"x-csrftoken\" value=\"{{request.state.csrftoken}}\">\n                <button type=\"submit\">Submit</button>\n            </form>\n            <script>\n                // For AJAX requests, you'd extract the csrftoken cookie and send it in the header\n                // Example (conceptual, requires frontend JS to read cookie):\n                // const csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrftoken=')).split('=')[1];\n                // fetch('/submit', {\n                //     method: 'POST',\n                //     headers: {\n                //         'Content-Type': 'application/x-www-form-urlencoded',\n                //         'x-csrftoken': csrfToken\n                //     },\n                //     body: 'item=ajax_test'\n                // });\n            </script>\n        </body>\n    </html>\n    '''\n\n@app.post(\"/submit\")\nasync def submit_item(item: str = Form(...), response: Response = None):\n    # The middleware automatically validates the token from the 'x-csrftoken' header\n    # If validation fails, it returns a 403 Forbidden before this handler is called.\n    return {\"message\": f\"Item '{item}' received successfully!\"}\n\nif __name__ == \"__main__\":\n    # To run: uvicorn your_app_file_name:app --reload\n    # Then open http://127.0.0.1:8000\n    uvicorn.run(app, host=\"127.0.0.1\", port=8000)\n","lang":"python","description":"This quickstart demonstrates how to integrate `CSRFMiddleware` into a FastAPI application. A `GET` request will automatically receive a `csrftoken` cookie and expose the token in `request.state.csrftoken`. For `POST` requests, the client is expected to include this token in an `x-csrftoken` header for successful validation. The example shows how to embed this in an HTML form."},"warnings":[{"fix":"Upgrade Python to 3.8 or newer. If unable to upgrade Python, pin `starlette-csrf` to `<3.0.0`.","message":"Version 3.0.0 dropped support for Python 3.7. Users on older Python versions must upgrade or remain on `starlette-csrf<3.0.0`.","severity":"breaking","affected_versions":">=3.0.0"},{"fix":"Ensure all optional arguments to `CSRFMiddleware` (e.g., `exempt_urls`, `cookie_name`) are passed as keyword arguments, not positional. Standard usage via `app.add_middleware` or `Middleware()` is generally unaffected.","message":"Starting with version 2.0.0, middleware initializer arguments (other than `app` and `secret`) became keyword-only. While this primarily affects direct instantiation, it's a breaking change for custom middleware setups.","severity":"breaking","affected_versions":">=2.0.0"},{"fix":"If you have custom middleware extending `CSRFMiddleware`, review your implementation for compatibility with the pure ASGI interface. For standard usage, no action is required.","message":"Version 1.4.4 rewrote the middleware as a pure ASGI middleware, moving away from `starlette.middleware.base.BaseHTTPMiddleware` (which is now deprecated in Starlette). While an improvement, this might affect advanced users who previously extended `CSRFMiddleware` and relied on `BaseHTTPMiddleware`'s internal structure or methods.","severity":"gotcha","affected_versions":">=1.4.4"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Ensure your frontend (HTML form or JavaScript AJAX request) correctly retrieves the `csrftoken` cookie value and sends it in the `x-csrftoken` HTTP header for all unsafe requests (POST, PUT, DELETE, PATCH). For HTML forms, you can inject `request.state.csrftoken` into a hidden input field. For AJAX, read the cookie and set the header manually.","cause":"The CSRF token expected in the 'x-csrftoken' header (or the configured header name) does not match the token in the 'csrftoken' cookie, or the header is missing entirely.","error":"403 Forbidden error on POST requests (or other unsafe methods)."},{"fix":"Exempt WebSocket routes from CSRF protection by adding them to the `exempt_urls` argument of the `CSRFMiddleware` using a regular expression pattern. Alternatively, wrap the middleware to conditionally apply it only to HTTP scopes, as suggested in some community discussions.","cause":"The `CSRFMiddleware` is designed for HTTP requests and may not handle WebSocket scopes correctly, leading to internal errors if it processes a WebSocket connection.","error":"WebSocket connections fail with `AssertionError` or other unexpected errors when `CSRFMiddleware` is applied globally."}]}