Skip to content

feat(middleware): add CSRF protection examples - #1379

Open
yoozzeek wants to merge 1 commit into
actix:mainfrom
yoozzeek:yoozzeek/csrf-examples
Open

feat(middleware): add CSRF protection examples#1379
yoozzeek wants to merge 1 commit into
actix:mainfrom
yoozzeek:yoozzeek/csrf-examples

Conversation

@yoozzeek

@yoozzeek yoozzeek commented Sep 8, 2026

Copy link
Copy Markdown

Add CSRF examples for the double submit cookie and synchronizer token patterns

The repository has no CSRF example. Actix Web ships no CSRF middleware and has not since the request was first filed (actix/actix-web#147, actix/actix-web#1185). This adds two middleware/ members, one per pattern documented in the OWASP CSRF Prevention Cheat Sheet, both built on actix-csrf-middleware 0.9. No existing example, workspace dependency, or CI job is touched. The crate is the only new package in Cargo.lock; all 17 of its dependencies already resolve in the workspace tree at HEAD.

Disclosure: I maintain actix-csrf-middleware.

Changes

middleware/csrf-double-submit: stateless, no session store

Files: middleware/csrf-double-submit/{Cargo.toml,README.md,src/main.rs}

The token lives in a cookie and is mirrored into a hidden csrf_token form field. The middleware compares the two in constant time and rejects any mutating request whose copies disagree. The cookie value is an HMAC over the session (or pre-session) identifier rather than a bare random string, which lets the server verify provenance and keeps an anonymous token from being accepted on an authenticated endpoint. Four routes: GET / renders the form, POST /login sets a session id cookie and calls rotate_csrf_after_login, POST /logout calls rotate_csrf_after_logout, and POST /message is the protected endpoint. Every mutating route is protected, login and logout included. Before sign-in the client carries a pre-session cookie and a CSRF-ANON token, covering registration and sign-in forms before a session exists. Cookie flags are the crate defaults (SameSite=Strict); only with_secure(false) is overridden, for plain-HTTP localhost.

middleware/csrf-synchronizer: stateful, backed by actix-session

Files: middleware/csrf-synchronizer/{Cargo.toml,README.md,src/main.rs}

The token is held server-side in the session and is never readable by client scripts, at the cost of requiring a session store. The example uses CookieSessionStore to stay self-contained. Routes match the double submit example. Two ordering constraints the example exists to demonstrate: SessionMiddleware is wrapped after CsrfMiddleware, making it the outer layer, which populates the session before the CSRF middleware runs; and Key::generate() is called once outside HttpServer::new, giving every worker one key. The session cookie is renamed to session, since actix-session and the middleware both default to id and, left colliding, classify every request authorized from the first response onward.

Workspace registration

Files: Cargo.toml, Cargo.lock

Adds the two members. The lock gains three [[package]] blocks (the two examples and actix-csrf-middleware 0.9.0) and nothing else.

Compatibility

  • No existing example, workspace dependency, or CI job is modified.
  • One new external package. Its 17 dependencies (hmac 0.13, sha2 0.11, subtle, zeroize, rand 0.10, url, hex, base64 0.22, and the actix crates) all already resolve in Cargo.lock at HEAD; the lock diff introduces no new transitive version.
  • Both members build on the workspace MSRV (rust-version = "1.98").

Testing

  • cargo check -p middleware-csrf-double-submit -p middleware-csrf-synchronizer: clean.
  • cargo clippy -p middleware-csrf-double-submit -p middleware-csrf-synchronizer --all-features --all-targets -- -D warnings: clean.
  • cargo +nightly fmt -p middleware-csrf-double-submit -p middleware-csrf-synchronizer -- --check: clean.
  • End-to-end over curl against both binaries, 18 assertions each, 36/36 passing. Anonymous GET / mints a token and sets pre-session. POST /message with no token returns 400 {"error":"csrf_token_missing"}; a forged token returns 400. POST /login returns 303, sets the session id cookie, and rotates the token. The stale anonymous token is rejected against the now-authenticated session. The authorized token returns 200 accepted: hello with Content-Type: text/plain; charset=utf-8, and the same token is accepted in an X-CSRF-Token header. POST /logout returns 303, the next GET / is anonymous again, and the pre-logout token no longer validates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant