CSRF Protection¶
This guide explains how CSRF works with pydantic-schemaforms, when to use it, and how to configure it safely in production.
What CSRF Is¶
Cross-Site Request Forgery (CSRF) is an attack where a malicious site causes a user browser to submit a request to your app while the user is authenticated.
Typical risk pattern:
- Your app uses cookie-based auth/session.
- A browser automatically sends those cookies with cross-site requests.
- An attacker tricks a user into submitting a state-changing request (
POST,PUT,DELETE, etc.).
CSRF protection adds an unguessable token that must be present and valid for each state-changing form submission.
How pydantic-schemaforms Uses CSRF¶
pydantic-schemaforms renders HTML forms and can include a hidden CSRF field. Your application code is responsible for:
- Generating a token.
- Storing/verifying token state (for example, in server-side session storage).
- Rejecting invalid submissions.
The library intentionally does not own your trust boundary.
CSRF Modes¶
csrf_mode controls form rendering behavior:
off: do not render a CSRF field.field-only: render CSRF field without requiring a provider.required-provider: require a token provider and render token field.
You can pass either:
- the string value (for example
"required-provider"), or - the enum value (
CSRFMode.REQUIRED_PROVIDER).
Important production guidance:
- Prefer
required-providerfor real security. field-onlyis compatibility/migration-oriented and is intentionally restricted:- explicit
csrf_mode="field-only"is only allowed withdebug=True. - Backward compatibility remains for legacy
include_csrf=Truebehavior.
Parameters¶
Relevant render options:
csrf_mode: one ofoff,field-only,required-provider.csrf_token_provider: token string or callable returning a token string.csrf_field_name: hidden input name (defaultcsrf_token).
Example:
from pydantic_schemaforms import CSRFMode
form_html = await render_form_html_async(
LoginForm,
form_data=form_data,
errors=errors,
submit_url="/login",
csrf_mode=CSRFMode.REQUIRED_PROVIDER,
csrf_token_provider=csrf_token,
csrf_field_name="csrf_token",
)
FastAPI Setup (Recommended Pattern)¶
import hmac
import secrets
from fastapi import FastAPI, Request
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="change-me")
CSRF_SESSION_KEY = "login_csrf_token"
def issue_csrf_token(request: Request) -> str:
token = secrets.token_urlsafe(32)
request.session[CSRF_SESSION_KEY] = token
return token
def verify_csrf_token(request: Request, submitted_token: str | None) -> bool:
expected = request.session.get(CSRF_SESSION_KEY)
if not expected or not submitted_token:
return False
return hmac.compare_digest(str(expected), str(submitted_token))
Flow:
- On
GET: issue token and render form withcsrf_mode="required-provider". - On
POST: extract submitted token, verify, reject with403if invalid. - On success: rotate/remove token.
Flask Setup (Equivalent Pattern)¶
Use Flask session storage and the same issue/verify steps:
- generate token on
GET. - render with
required-provider. - verify on
POSTbefore validation.
When CSRF Applies (And When It Usually Does Not)¶
CSRF usually applies:
- Browser-based forms.
- Cookie/session auth.
- State-changing endpoints.
CSRF often not applicable:
- Pure machine-to-machine APIs using bearer tokens in
Authorizationheader. - Non-browser clients that do not auto-send ambient cookies.
- Read-only endpoints (
GET) with no state changes.
Even when CSRF may be less relevant, keep standard API authz/authn controls in place.
Security Benefits¶
Using CSRF tokens helps:
- block cross-site forged submissions.
- ensure request intent from pages your app rendered.
- reduce account/action takeover risk in session-based browser workflows.
Logging Notes¶
By default, CSRF validation in example integrations surfaces user-facing errors but does not log token values.
Best practice:
- never log raw CSRF tokens.
- log only outcome/context (for example,
csrf_failed, route, request id, user id if available).
Production Checklist¶
- Use
csrf_mode="required-provider". - Verify token before processing form payload.
- Use constant-time comparison (
hmac.compare_digest). - Rotate tokens appropriately.
- Keep session secret strong and private.
- Use HTTPS in production.
- Avoid token leakage in logs.