microdot.csrf — CSRF protection

Rejects state-changing requests that originate cross-site. The check runs as a before_request hook and uses the browser-supplied Sec-Fetch-Site header (with the Origin header as a fallback for older browsers, when paired with a microdot.cors.CORS instance).

class CSRF

class microdot.csrf.CSRF(app: Microdot | None = None, cors=None, protect_all: bool = True, allow_subdomains: bool = False)
app

The microdot.Microdot instance to install on. Optional; call initialize() later if not given.

cors

The microdot.cors.CORS instance that defines the application’s trusted origins. Required for falling back to the Origin header on browsers that do not send Sec-Fetch-Site. Optional; without it, only the Sec-Fetch-Site check applies.

protect_all

If True (default), every route except GET, HEAD, and OPTIONS is protected automatically. Routes can be exempted individually with exempt(). If False, no routes are protected by default and individual routes opt in with protect().

allow_subdomains

If True, requests from subdomains of the application’s origins are accepted (same-site Sec-Fetch-Site, or matching origin suffix).

initialize(app: Microdot, cors=None)

Attach to app if construction was deferred.

exempt(f)

Decorator that exempts a route from CSRF protection. Place it directly after the route decorator:

@app.post('/webhook')
@csrf.exempt
async def webhook(request):
    # accepts cross-site POSTs
protect(f)

Decorator that forces CSRF protection on a route that would otherwise be exempt (e.g. a GET route or every route when protect_all=False).

SAFE_METHODS: list

Class attribute listing methods that are not protected by default – ['GET', 'HEAD', 'OPTIONS'].

Example:

from microdot import Microdot
from microdot.cors import CORS
from microdot.csrf import CSRF

app = Microdot()
cors = CORS(app, allowed_origins=['https://app.example.com'])
csrf = CSRF(app, cors=cors)

@app.post('/api/save')
async def save(request):
    # automatically CSRF-protected
    return {'ok': True}

A request is allowed when Sec-Fetch-Site reports same-origin or none (or same-site when allow_subdomains=True). If the header is absent, the request’s Origin is matched against the CORS allow-list. Requests with neither header are accepted (typical for direct API calls that aren’t subject to browser CSRF).