axum_csrf 0.6.0

Library to Provide a CSRF (Cross-Site Request Forgery) protection layer.
Documentation

Help

If you need help with this library please join our Discord Group

Install

# Cargo.toml

[dependencies]

axum_csrf = "0.6.0"

Example

Add it to axum via shared state:

#[tokio::main]
async fn main() {

    // Build our application with some routes
    let app = Router::with_state(CsrfConfig::default())
        .route("/greet", get(greet))
        .route("/check_key", post(check_key))

    // Serve the application at http://localhost:3000/
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

If you already have an encryption key for private cookies, build the CSRF configuration a different way:

let cookie_key = cookie::Key::generate();
let config = CsrfConfig::default().with_key(Some(cookie_key));

let app = Router::new().with_state(config)

Add the attribute to your form view template context struct:

#[derive(Template, Deserialize, Serialize)]
#[template(path = "hello.html")]
struct Keys {
    authenticity_token: String,
    // Your attributes...
}

Retrieve the CSRF token, extracted as axum request handler parameter. Insert its authenticity token hash into the form view's template context:

async fn greet(token: CsrfToken) -> impl IntoResponse {
    let keys = Keys {
        authenticity_token: token.authenticity_token(),
    }

    // We must return the token so that into_response will run and add it to our response cookies.
    (token, HtmlTemplate(keys))
}

Insert the authenticity token into the HTML template as hidden form field:

<form method="post" action="/check_key">
    <input type="hidden" name="authenticity_token" value="{{ authenticity_token }}"/>
    <!-- your fields -->
</form>

Validate the CSRF key upon receiving the POSTed form data:

async fn check_key(token: CsrfToken, Form(payload): Form<Keys>,) -> &'static str {
    if let Err(_) = token.verify(&payload.authenticity_token) {
        "Token is invalid"
    } else {
        "Token is Valid lets do stuff!"
    }
}

Prevent Post Replay Attacks with CSRF.

If you want to Prevent Post Replay Attacks then you should use a Session Storage method. you store the hash in the server side session store as well as send it with the form. when they post the data you would check the hash of the form first and then against the internal session data 2nd. After the 2nd hash is valid you would then remove the hash from the session. This prevents replay attacks and ensure no data was manipulated. If you need a Session database I would suggest using axum_database_sessions

Changes using axum_database_sessions.

async fn greet(token: CsrfToken, sessions: AxumSession) -> impl IntoResponse {
    let authenticity_token = token.authenticity_token();
    session.set("authenticity_token", authenticity_token.clone()).await;

    let keys = Keys {
        authenticity_token,
    }

    //we must return the token so that into_response will run and add it to our response cookies.
    (token, HtmlTemplate(keys))
}

Validate the CSRF Key and Validate for Post Replay attacks

async fn check_key(token: CsrfToken, sessions: AxumSession, Form(payload): Form<Keys>,) -> &'static str {
    let authenticity_token: String = session.get("authenticity_token").await.unwrap_or_default();

    if let Err(_) = token.verify(&payload.authenticity_token) {
        "Token is invalid"
    } else if let Err(_) = token.verify(&authenticity_token) {
        "Modification of both Cookie/token OR a replay attack occured"
    } else {
        // we remove it to only allow one post per generated token.
        session.remove("authenticity_token").await;
        "Token is Valid lets do stuff!"
    }
}