📝 add start of middleware docs & CSRF doc

This commit is contained in:
Joe Kaufeld 2024-08-27 17:44:36 -04:00
parent 01ae25240c
commit 2f31ad612c
4 changed files with 116 additions and 2 deletions

View File

@ -0,0 +1,102 @@
# csrf middleware
```python
from spiderweb import SpiderwebRouter
app = SpiderwebRouter(
middleware=[
"spiderweb.middleware.sessions.SessionMiddleware",
"spiderweb.middleware.csrf.CSRFMiddleware",
],
)
```
> [!DANGER]
> The CSRFMiddleware is incomplete at best and dangerous at worst. I am not a security expert, and my implementation is [very susceptible to the thing it is meant to prevent](https://en.wikipedia.org/wiki/Cross-site_request_forgery). While this is an big issue (and moderately hilarious), the middleware is still provided to you in its unfinished state. Be aware.
Cross-site request forgery, put simply, is a method for attackers to make legitimate-looking requests in your name to a service or system that you've previously authenticated to. Ways that we can protect against this involve aggressively expiring session cookies, special IDs for forms that are keyed to a specific user, and more.
Notice that in the example above, SessionMiddleware is also included in the middleware list. The CSRF middleware requires the SessionMiddleware to function, and SessionMiddleware must be placed above it in the middleware list.
## CSRF and Forms
When you create a form, submitting data to the form is the part where things can go wrong. The CSRF middleware grants you two extra pieces in the TemplateResponse response: `csrf_token` and `csrf_token_raw`. `csrf_token` is a preformatted HTML input with preset attributes, ready for use, that you can drop into your template, while `csrf_token_raw` is the token itself with no extra formatting in case you'd like to do something else with it.
Here's an example app that renders a form with two input fields and a checkbox, accepts the form data, and sends back the information as JSON.
```python
# myapp.py
from spiderweb import SpiderwebRouter
from spiderweb.response import JsonResponse, TemplateResponse
app = SpiderwebRouter(
templates_dirs=["templates"],
middleware=[
"spiderweb.middleware.sessions.SessionMiddleware",
"spiderweb.middleware.csrf.CSRFMiddleware",
],
)
@app.route("/", allowed_methods=["GET", "POST"])
def form(request):
if request.method == "POST":
return JsonResponse(data=request.POST)
else:
return TemplateResponse(request, "form.html")
```
```html
<!-- templates/form.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap demo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
<div class="container">
<form action="" method="post">
<div class="mb-3">
<input type="email" class="form-control" name="email" id="emailInput" placeholder="name@example.com">
</div>
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">Example textarea</label>
<textarea class="form-control" name="comment" id="exampleFormControlTextarea1" rows="3"></textarea>
</div>
<div class="mb-3 form-check">
<input type="checkbox" name="formcheck" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">Check me out</label>
</div>
{{ csrf_token }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
</body>
</html>
```
With this complete app, it will serve and accept the form. Note towards the bottom of `form.html` the line `{{ csrf_token }}` — this is the inserted key. All you need to do is make sure that line is included inside your form and it will be accepted and parsed.
## Marking views as CSRF-Exempt
```python
from spiderweb.decorators import csrf_exempt
```
If you want to accept POST data at an endpoint with CSRF verification enabled, you will need to mark the endpoint as CSRF-exempt. Spiderweb provides a decorator for this use case:
```python
@csrf_exempt
@app.route("/form", allowed_methods=["GET", "POST"])
def form(request):
...
```
Just drop it above the route information for your function and Spiderweb will not check for a CSRF token when form data is submitted.

View File

@ -16,4 +16,13 @@ app = SpiderwebRouter(
) )
``` ```
Middleware affects both the incoming request AND the outgoing response, so the order that it's defined here is important. When a request comes in, it will be processed through the middleware in the order that it's defined, but after the response is created, it will pass back through the middleware going the opposite direction. Middleware affects both the incoming request AND the outgoing response, so the order that it's defined here is important. When a request comes in, it will be processed through the middleware in the order that it's defined, but after the response is created, it will pass back through the middleware going the opposite direction. Each piece of middleware has two functions: `process_request` and `process_response`.
When a request comes in, after building the request object, Spiderweb will start running through the middleware from the top down. In the example above, that means that it will call `process_request()` on the SessionMiddleware, then CSRFMiddleware, and finally PydanticMiddleware. At this point, control is passed to the view so that you can run your application-specific logic. As part of the view, you'll return a response, and Spiderweb will take over again.
Now that Spiderweb has the response object, it will start from the bottom of the stack (PydanticMiddleware in the example) and call `process_response()`, working its way up back to the top and ending with the `process_response()` of SessionMiddleware.
> [!NOTE]
> Middleware must be declared when instantiating the server; they can't be added on afterward. Once the server has been started, every piece of middleware will run for every request and every response.
There are a few pieces of middleware built into Spiderweb that you can immediately put to use; they're described on the following pages.

View File

@ -101,6 +101,9 @@ def index(request):
) )
``` ```
> [!TIP]
> You can [read more about crafting templates for Jinja here!](https://jinja.palletsprojects.com/en/3.0.x/templates/)
## RedirectResponse ## RedirectResponse
```python ```python

View File

@ -14,7 +14,7 @@
<input type="checkbox" name="formcheck" class="form-check-input" id="exampleCheck1"> <input type="checkbox" name="formcheck" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">Check me out</label> <label class="form-check-label" for="exampleCheck1">Check me out</label>
</div> </div>
<!-- {{ csrf_token }}--> {{ csrf_token }}
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary">Submit</button>
</form> </form>