Crate tower_sec_fetch

Source
Expand description

§Cookieless CSRF protection library

This crate provides a Tower middleware that implements Cross-Site-Request-Forgery protection by validating the Fetch Metadata headers of the incoming HTTP request. It does not require cookies, or signing keys, or tokens.

If you’re looking for a classic CSRF cookie implementation, try tower-surf instead.

§Overview

For a more in-depth explanation of the problem CSRF protection is trying to solve, and why using signed cookies is not always the best solution, refer to this excellent writeup by Filippo Valsorda.

In short, this crate allows to protect web resources from cross-site inclusion and abuse by validating the Fetch Metadata headers and ensuring that only “safe” cross-site requests are allowed. In this context, “safe” means:

  • the request comes from the same origin (the site’s exact scheme, host, and port), same site (any subdomain of the current domain), or are user-initiated (e.g. clicking on a bookmark, directly entering the website’s address), OR…
  • the request is a simple GET request coming from a navigation event (e.g. clicking on a link on another website), as long as it’s not being embedded in elements like <object> or <iframe>.

If the request does not include the Fetch Metadata, such as a request coming from a non-browser user-agent, or a browser released before 2023, the request will be accepted.

You can change this behaviour by setting the reject_missing_metadata flag on the evaluation policy, but it might make your website not accessible to some users. Note that this is not a good protection against non-browser clients, as they can set the necessary headers anyway.

§Usage

Add the library to your Cargo.toml

[dependencies]
tower-sec-fetch = "*"

Here’s how to use it with Axum, but it works with any tower-based server.

let routes = axum::Router::new()
    .route("/hello", get(async || "hello"))
    .layer(SecFetchLayer::default());

Specific paths can be explicitely allowed.

let routes = axum::Router::new()
    .route("/hello", get(async || "hello"))
    .route("/unprotected", get(async || "unprotected"))
    .layer(SecFetchLayer::default().allowing(["/unprotected"]));

You can override the default authorization logic with a custom SecFetchAuthorizer.

use tower_sec_fetch::{AuthorizationDecision, SecFetchAuthorizer, SecFetchLayer};

struct MyAuthorizer;

impl SecFetchAuthorizer for MyAuthorizer {
   fn authorize<B>(&self, request: &http::Request<B>) -> AuthorizationDecision {
       // allow all requests that come from a specific domain
       if request.uri().host() == Some("my-domain.com") {
           return AuthorizationDecision::Allowed;
       }

       // otherwise, continue with the regular evaluation policy
       AuthorizationDecision::Continue
   }
}

SecFetchLayer::default().with_authorizer(MyAuthorizer);

You can provide a SecFetchReporter implementation to be notified of a request being blocked. This can be useful for analytics and monitoring, but also to incrementally introduce this middleware in an existing system where there might be the risk of blocking legitimate requests by accident, when combined with the no_enforce flag.

use tower_sec_fetch::{SecFetchLayer, SecFetchReporter};

struct LogReporter;

impl SecFetchReporter for LogReporter {
    fn on_request_denied<B>(&self, request: &http::Request<B>) {
        let uri = request.uri();
        let method = request.method();
        let headers = request.headers();

        eprintln!("request was denied: {method} {uri} {headers:?}");
    }
}

SecFetchLayer::default().no_enforce().with_reporter(LogReporter);

Safe methods are not allowed for cross-origin requests, but this can optionally be disabled by setting the allow_safe_methods flag on the evaluation policy.

SecFetchLayer::new(|policy| {
    policy.allow_safe_methods();
});

If the Fetch Metadata headers are missing, the request is allowed. This can be disabled by setting the reject_missing_metadata flag on the evaluation policy.

SecFetchLayer::new(|policy| {
    policy.reject_missing_metadata();
});

Modules§

header

Structs§

PathAuthorizer
A SecFetchAuthorizer that allows requests based on their path
PolicyBuilder
Allows customizing the behaviour of the default evaluation policy
SecFetch
Middleware protecting against CSRF attacks
SecFetchLayer
Layer that applies SecFetch which validates request against CSRF attacks

Enums§

AuthorizationDecision
The decision made by a SecFetchAuthorizer

Traits§

SecFetchAuthorizer
Custom request authorization logic
SecFetchReporter
Notifies of requests being blocked by this middleware