Use when you have a written implementation plan to execute in a separate session with review checkpoints
npx skills add salvo-rs/salvo-skills --skill "salvo-csrf"
Install specific skill from multi-skill repository
# Description
Implement CSRF (Cross-Site Request Forgery) protection using cookie or session storage. Use for protecting forms and state-changing endpoints.
# SKILL.md
name: salvo-csrf
description: Implement CSRF (Cross-Site Request Forgery) protection using cookie or session storage. Use for protecting forms and state-changing endpoints.
Salvo CSRF Protection
This skill helps implement CSRF protection in Salvo applications to prevent cross-site request forgery attacks.
What is CSRF?
Cross-Site Request Forgery (CSRF) is an attack that tricks users into executing unwanted actions on a web application where they're authenticated. CSRF protection ensures that form submissions come from your own site.
Setup
[dependencies]
salvo = { version = "1.88.1", features = ["csrf"] }
CSRF Protection Methods
Salvo provides four cryptographic methods for CSRF token generation:
| Method | Description | Performance |
|---|---|---|
| Bcrypt | Slow hashing, no key needed | Slowest |
| HMAC | Fast, requires 32-byte key | Fast |
| AES-GCM | Authenticated encryption, 32-byte key | Fast |
| ChaCha20Poly1305 | Modern encryption, 32-byte key | Fast |
Basic CSRF with Cookie Store
use salvo::csrf::*;
use salvo::prelude::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct FormData {
csrf_token: String,
message: String,
}
#[handler]
async fn show_form(depot: &mut Depot, res: &mut Response) {
let token = depot.csrf_token().unwrap_or_default();
res.render(Text::Html(format!(r#"
<form method="post">
<input type="hidden" name="csrf_token" value="{token}" />
<input type="text" name="message" />
<button type="submit">Submit</button>
</form>
"#)));
}
#[handler]
async fn handle_form(req: &mut Request, res: &mut Response) {
let data = req.parse_form::<FormData>().await.unwrap();
res.render(format!("Message received: {}", data.message));
}
#[tokio::main]
async fn main() {
// Configure where to find CSRF token in requests
let form_finder = FormFinder::new("csrf_token");
// Create CSRF handler with bcrypt (no key required)
let csrf_handler = bcrypt_cookie_csrf(form_finder);
let router = Router::new()
.hoop(csrf_handler)
.get(show_form)
.post(handle_form);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
CSRF Methods with Cookie Store
Bcrypt (No Key Required)
use salvo::csrf::{bcrypt_cookie_csrf, FormFinder};
let form_finder = FormFinder::new("csrf_token");
let csrf_handler = bcrypt_cookie_csrf(form_finder);
HMAC (32-byte Key)
use salvo::csrf::{hmac_cookie_csrf, FormFinder};
let key = *b"01234567012345670123456701234567"; // 32 bytes
let form_finder = FormFinder::new("csrf_token");
let csrf_handler = hmac_cookie_csrf(key, form_finder);
AES-GCM (32-byte Key)
use salvo::csrf::{aes_gcm_cookie_csrf, FormFinder};
let key = *b"01234567012345670123456701234567"; // 32 bytes
let form_finder = FormFinder::new("csrf_token");
let csrf_handler = aes_gcm_cookie_csrf(key, form_finder);
ChaCha20Poly1305 (32-byte Key)
use salvo::csrf::{ccp_cookie_csrf, FormFinder};
let key = *b"01234567012345670123456701234567"; // 32 bytes
let form_finder = FormFinder::new("csrf_token");
let csrf_handler = ccp_cookie_csrf(key, form_finder);
CSRF with Session Store
For session-based CSRF storage, combine with SessionHandler:
use salvo::csrf::*;
use salvo::session::{CookieStore, SessionHandler};
use salvo::prelude::*;
#[tokio::main]
async fn main() {
// Session handler (required for session-based CSRF)
let session_handler = SessionHandler::builder(
CookieStore::new(),
b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
)
.build()
.unwrap();
let form_finder = FormFinder::new("csrf_token");
// Use session-based CSRF storage
let csrf_handler = bcrypt_session_csrf(form_finder);
let router = Router::new()
.hoop(session_handler) // Session must come first
.hoop(csrf_handler)
.get(show_form)
.post(handle_form);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Session Store Methods
// Bcrypt
let csrf = bcrypt_session_csrf(form_finder);
// HMAC
let csrf = hmac_session_csrf(key, form_finder);
// AES-GCM
let csrf = aes_gcm_session_csrf(key, form_finder);
// ChaCha20Poly1305
let csrf = ccp_session_csrf(key, form_finder);
Token Finders
Form Finder (POST Body)
use salvo::csrf::FormFinder;
// Look for "csrf_token" in form data
let finder = FormFinder::new("csrf_token");
Header Finder
use salvo::csrf::HeaderFinder;
// Look for "X-CSRF-Token" header
let finder = HeaderFinder::new("X-CSRF-Token");
Query Finder
use salvo::csrf::QueryFinder;
// Look for "csrf_token" in query string
let finder = QueryFinder::new("csrf_token");
Getting CSRF Token
Use CsrfDepotExt to get the CSRF token in handlers:
use salvo::csrf::CsrfDepotExt;
#[handler]
async fn show_form(depot: &mut Depot, res: &mut Response) {
// Get CSRF token for the form
let token = depot.csrf_token().unwrap_or_default();
res.render(Text::Html(format!(r#"
<form method="post">
<input type="hidden" name="csrf_token" value="{token}" />
<!-- form fields -->
</form>
"#)));
}
Multiple CSRF Methods
Apply different CSRF methods to different routes:
let form_finder = FormFinder::new("csrf_token");
let bcrypt_csrf = bcrypt_cookie_csrf(form_finder.clone());
let hmac_csrf = hmac_cookie_csrf(*b"01234567012345670123456701234567", form_finder.clone());
let router = Router::new()
.push(
Router::with_hoop(bcrypt_csrf)
.path("forms")
.get(show_form)
.post(handle_form)
)
.push(
Router::with_hoop(hmac_csrf)
.path("api")
.get(get_token)
.post(api_handler)
);
CSRF for AJAX Requests
For AJAX/fetch requests, use header-based CSRF:
use salvo::csrf::{HeaderFinder, hmac_cookie_csrf};
let header_finder = HeaderFinder::new("X-CSRF-Token");
let csrf_handler = hmac_cookie_csrf(*b"01234567012345670123456701234567", header_finder);
// Client JavaScript:
// fetch('/api', {
// method: 'POST',
// headers: { 'X-CSRF-Token': token },
// body: JSON.stringify(data)
// });
Complete Example
use salvo::csrf::*;
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct FormData {
csrf_token: String,
message: String,
}
#[handler]
async fn index(res: &mut Response) {
res.render(Text::Html(r#"
<h1>CSRF Protection Demo</h1>
<ul>
<li><a href="/form">Protected Form</a></li>
</ul>
"#));
}
#[handler]
async fn show_form(depot: &mut Depot, res: &mut Response) {
let token = depot.csrf_token().unwrap_or_default();
res.render(Text::Html(format!(r#"
<!DOCTYPE html>
<html>
<body>
<h2>Submit Message</h2>
<form method="post">
<input type="hidden" name="csrf_token" value="{token}" />
<label>Message: <input type="text" name="message" /></label>
<button type="submit">Send</button>
</form>
</body>
</html>
"#)));
}
#[handler]
async fn handle_form(req: &mut Request, depot: &mut Depot, res: &mut Response) {
match req.parse_form::<FormData>().await {
Ok(data) => {
// Generate new token for next request
let new_token = depot.csrf_token().unwrap_or_default();
res.render(Text::Html(format!(
"Received: {} <br><a href='/form'>Back</a>",
data.message
)));
}
Err(e) => {
res.status_code(StatusCode::BAD_REQUEST);
res.render(format!("Error: {}", e));
}
}
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let form_finder = FormFinder::new("csrf_token");
let csrf_handler = hmac_cookie_csrf(
*b"01234567012345670123456701234567",
form_finder,
);
let router = Router::new()
.get(index)
.push(
Router::with_hoop(csrf_handler)
.path("form")
.get(show_form)
.post(handle_form)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Best Practices
- Use HMAC or AES-GCM in production: Bcrypt is slow; use faster methods with a secure key
- Generate secure keys: Use cryptographically secure random bytes for keys
- Session store for sensitive apps: Session-based storage is more secure than cookie-based
- Include token in all forms: Every state-changing form needs a CSRF token
- Validate on all state-changing requests: POST, PUT, DELETE, PATCH all need protection
- Use SameSite cookies: Combine CSRF with SameSite=Strict cookies for extra protection
- Rotate tokens: Generate new tokens after successful form submission
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.