Skip to main content

Crate trillium_csrf

Crate trillium_csrf 

Source
Expand description

Cross-site request forgery (CSRF) protection for trillium.

This handler rejects state-changing cross-origin requests using metadata that browsers attach to every request. It needs no tokens, no cookies, and no configuration to protect an app whose frontend and api share an origin:

use trillium_csrf::csrf;

let app = (
    csrf(),
    |conn: trillium::Conn| async move { conn.ok("hello") },
);

For each request, in order:

  • GET, HEAD, and OPTIONS requests are always allowed.
  • If the request has a Sec-Fetch-Site header, it is allowed when the value is same-origin or none (a user-initiated request such as a bookmark or a typed address) and otherwise rejected, unless the Origin header is trusted (see Csrf::with_trusted_origins).
  • Without Sec-Fetch-Site but with an Origin header, the request is allowed when the origin’s host and port match the request’s own host and otherwise rejected, unless the origin is trusted. Schemes are not compared, so this behaves correctly behind a tls-terminating reverse proxy.
  • A request with neither header is allowed: it did not come from a browser, so it cannot carry a browser’s ambient credentials, and cross-site request forgery does not apply.

Rejections halt the conn with a 403 status and log the check that failed along with the configuration that would allow the request if it was legitimate.

The allowed-method list is exactly GET, HEAD, and OPTIONS, and is not configurable. It exists because browsers send those methods ambiently — navigations, images, plain forms — without a CORS preflight, so rejecting them cross-origin would break ordinary links to your site. Other methods that http defines as safe, such as QUERY, stay protected: a browser only sends them cross-origin after a preflight your server already controls, so exempting them here would trust every handler’s implementation without enabling any request that works today.

§Exempting a route

Webhook endpoints don’t need an exemption: webhook senders are not browsers, send neither header, and are allowed. If a route must accept browser requests from origins you can’t enumerate — a multi-tenant single-sign-on callback, say — run this handler conditionally by wrapping it:

use trillium::{Conn, Handler};
use trillium_csrf::{Csrf, csrf};

struct ExemptSsoCallback(Csrf);

impl Handler for ExemptSsoCallback {
    async fn run(&self, conn: Conn) -> Conn {
        if conn.path() == "/sso/callback" {
            conn
        } else {
            self.0.run(conn).await
        }
    }
}

let handler = ExemptSsoCallback(csrf());

§What this does not cover

Browsers released before roughly 2019 may send neither Sec-Fetch-Site nor Origin on cross-site form submissions, and this handler allows those requests. Protecting that population requires request tokens, which this crate does not provide. For the reasoning behind header-based protection, see Cross-Site Request Forgery. If you need token support, open an issue.

Apis authenticated exclusively by a bearer token or other explicit request header don’t need this crate: cross-site request forgery is only possible when authentication is ambient, as with cookies or network position.

Structs§

Csrf
A Handler that rejects state-changing cross-origin requests.

Functions§

csrf
Constructs a new Csrf handler with no trusted origins.