Expand description

§Axum Synchronizer Token Pattern CSRF prevention

This crate provides a CSRF protection layer and middleware for use with the axum web framework.

The middleware implements the CSRF Synchronizer Token Pattern for AJAX backends and API endpoints as described in the OWASP CSRF prevention cheat sheet.

§Scope

This middleware implements token transfer via custom request headers.

The middleware requires and is built upon axum_sessions, which in turn uses async_session.

The Same Origin Policy prevents the custom request header to be set by foreign scripts.

§In which contexts should I use this middleware?

The goal of this middleware is to prevent cross-site request forgery attacks specifically in applications communicating with their backend by means of the JavaScript fetch() API or classic XmlHttpRequest, traditionally called “AJAX”.

The Synchronizer Token Pattern is especially useful in CORS contexts, as the underlying session cookie is obligatorily secured and inaccessible by JavaScript, while the custom HTTP response header carrying the CSRF token can be exposed using the CORS Access-Control-Expose-Headers HTTP response header.

While the Same Origin Policy commonly prevents custom request headers to be set on cross-origin requests, use of the use of the Access-Control-Allow-Headers CORS HTTP response header can be used to specifically allow CORS requests to be equipped with a required custom HTTP request header.

This approach ensures that requests forged by auto-submitted forms or other data-submitting scripts from foreign origins are unable to add the required header.

§When should I use other CSRF protection patterns or libraries?

Use other available middleware libraries if you plan on submitting classical HTML forms without the use of JavaScript, and if you do not send the form data across origins.

§Security

§Token randomness

The CSRF tokens are generated using rand::ThreadRng which is considered cryptographically secure (CSPRNG). See “Our RNGs” for more.

§Underlying session security

The security of the underlying session is paramount - the CSRF prevention methods applied can only be as secure as the session carrying the server-side token.

  • When creating your SessionLayer, make sure to use at least 64 bytes of cryptographically secure randomness.
  • Do not lower the secure defaults: Keep the session cookie’s secure flag on.
  • Use the strictest possible same-site policy.

§CORS security

If you need to provide and secure cross-site requests:

  • Allow only your backend origin when configuring the CorsLayer
  • Allow only the headers you need. (At least the CSRF request token header.)
  • Only expose the headers you need. (At least the CSRF response token header.)

§No leaks of error details

Errors are logged using tracing::error!. Error responses do not contain error details.

Use tower_http::TraceLayer to capture and view traces.

§Safety

This crate uses no unsafe code.

The layer and middleware functionality is tested. View the the module source code to learn more.

§Usage

See the example projects for same-site and cross-site usage.

§Same-site usage

Note: The crate repository contains example projects for same-site and cross-site usage!

Configure your session and CSRF protection layer in your backend application:

use axum::{
    body::Body,
    http::StatusCode,
    routing::{get, Router},
};
use axum_csrf_sync_pattern::{CsrfLayer, RegenerateToken};
use axum_sessions::{async_session::MemoryStore, SessionLayer};
use rand::RngCore;

let mut secret = [0; 64];
rand::thread_rng().try_fill_bytes(&mut secret).unwrap();

async fn handler() -> StatusCode {
    StatusCode::OK
}

let app = Router::new()
    .route("/", get(handler).post(handler))
    .layer(
        CsrfLayer::new()

        // Optionally, configure the layer with the following options:

        // Default: RegenerateToken::PerSession
        .regenerate(RegenerateToken::PerUse)
        // Default: "X-CSRF-TOKEN"
        .request_header("X-Custom-Request-Header")
        // Default: "X-CSRF-TOKEN"
        .response_header("X-Custom-Response-Header")
        // Default: "_csrf_token"
        .session_key("_custom_session_key")
    )
    .layer(SessionLayer::new(MemoryStore::new(), &secret));

// Use hyper to run `app` as service and expose on a local port or socket.

Receive the token and send same-site requests, using your custom header:

const test = async () => {
    // Receive CSRF token (Default response header name: 'X-CSRF-TOKEN')
    const token = (await fetch('/')).headers.get('X-Custom-Response-Header');

    // Submit data using the token
    await fetch('/', {
        method: 'POST',
        headers: {
           'Content-Type': 'application/json',
           // Default request header name: 'X-CSRF-TOKEN'
           'X-Custom-Request-Header': token,
        },
        body: JSON.stringify({ /* ... */ }),
    });
};

§CORS-enabled usage

Note: The crate repository contains example projects for same-site and cross-site usage!

Configure your CORS layer, session and CSRF protection layer in your backend application:

use axum::{
    body::Body,
    http::{header, Method, StatusCode},
    routing::{get, Router},
};
use axum_csrf_sync_pattern::{CsrfLayer, RegenerateToken};
use axum_sessions::{async_session::MemoryStore, SessionLayer};
use rand::RngCore;
use tower_http::cors::{AllowOrigin, CorsLayer};

let mut secret = [0; 64];
rand::thread_rng().try_fill_bytes(&mut secret).unwrap();

async fn handler() -> StatusCode {
    StatusCode::OK
}

let app = Router::new()
    .route("/", get(handler).post(handler))
    .layer(
        // See example above for custom layer configuration.
        CsrfLayer::new()
    )
    .layer(SessionLayer::new(MemoryStore::new(), &secret))
    .layer(
        CorsLayer::new()
            .allow_origin(AllowOrigin::list(["https://www.example.com".parse().unwrap()]))
            .allow_methods([Method::GET, Method::POST])
            .allow_headers([header::CONTENT_TYPE, "X-CSRF-TOKEN".parse().unwrap()])
            .allow_credentials(true)
            .expose_headers(["X-CSRF-TOKEN".parse().unwrap()]),
   );

// Use hyper to run `app` as service and expose on a local port or socket.

Receive the token and send cross-site requests, using your custom header:

const test = async () => {
    // Receive CSRF token
    const token = (await fetch('https://backend.example.com/', {
        credentials: 'include',
    })).headers.get('X-CSRF-TOKEN');

    // Submit data using the token
    await fetch('https://backend.example.com/', {
        method: 'POST',
        headers: {
           'Content-Type': 'application/json',
           'X-CSRF-TOKEN': token,
        },
        credentials: 'include',
        body: JSON.stringify({ /* ... */ }),
    });
};

§Contributing

Pull requests are welcome!

Structs§

  • Use CsrfLayer::new() to provide the middleware and configuration to axum’s service stack.
  • This middleware is created by axum by applying the CsrfLayer. It verifies the CSRF token header on incoming requests, regenerates tokens as configured, and attaches the current token to the outgoing response.

Enums§