3 releases
new 0.1.3 | Mar 2, 2025 |
---|---|
0.1.1 | Mar 2, 2025 |
0.1.0 | Mar 2, 2025 |
#369 in Cryptography
17KB
194 lines
axum-csrf
Simple, automatic CSRF handling for Axum
Concept
All requests that has no CSRF cookie will get set-cookie header automatically.
All state changing requests (PUT,DELETE,POST,PATCH) will request x-csrf-token
with a valid header.
The x-csrf-token
is compared with the request cookie csrf_token
. If not matched, request will be rejected with 401
.
The rejection body will tell the reason.
No need to do any other change
HTTP Form post hidden field of csrf_token?
The code does not inspect POST param for CSRF retrieval. This is generally very expensive and less convenient. If you need to use post
you can append form action with query string like ?csrf_token=xxxx
to submit.
If both x-csrf-token
header and ?csrf_token=xxxx
query string are specified, the x-csrf-token
will be used.
Key feature
- Fully automated. PUT, DELETE, POST, PATCH requests always has CSRF protection.
- Resilient. Requests that does not have CSRF cookie will get a new token automatically
- Easy to use. Simply
GET /api/get_csrf_token
to retrieve the token programmatically, and use it in header or query_string when submitting.
How to use
dependency
Add the dependency:
$ cargo add axum-csrf-simple
server side
/// import as alias (optional)
use axum_csrf_simple as csrf;
/// Set your preferred token signing key.
/// All token is <uuid>-<hmac_signature> format. Invalid tokens won't be trusted.
// if not set, the default signing key is 32 char auto generated signed key
// note token signed by different sign key will be invalid.
// if you did not specify the signing key, it could be an issue if you have multiple instance of apps
// as the token won't be compatible with other server's token
csrf::set_csrf_token_sign_key("my-signing-key").await;
// set to true to only transfer CSRF cookie over https (for dev, set to false or it won't work - false is the default)
csrf::set_csrf_secure_cookie_enable(true);
let app = Router::new()
.route("/admin/endpoint1", get(handle1).post(handle1).put(handle1)) // this url, when POST/PUT, is CSRF protected
.route("/api/get_csrf_token", get(csrf::get_csrf_token)) // expose the API for your client to retrieve CSRF token. One time only, and it can be cached.
.route_layer(middleware::from_fn(csrf::csrf_protect));
/// Test accessing /admin/endpoint1 with post, it will require CSRF validation.
Note the /api/get_csrf_token
API uri can be customized based on your need.
client side
Client has to submit request with x-csrf-token
header value to be able to submit successfully.
Retrieving CSRF token
Example using plain javascript.
It is pretty easy to write for react, angular, nodejs.
The result payload of the /api/get_csrf_token
is like
{
"token":"xzP14JrA9qHFaLwKHpFYYj-a209dda82c379b55542b4ed2f977e5464be79b8b7f2009ecb08d36b92599b13f",
"is_new":false
}
token: The token
is_new: Tells if the server did create a new token for client
The token can't be used across servers with different signature key.
When requesting the token, server automatically set a cookie csrf_token.
<script>
var CSRF_TOKEN = "";
(function() {
fetch("/api/get_csrf_token")
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json(); // Convert response to JSON
})
.then(data => {
console.log("CSRF Token:", data.token);
CSRF_TOKEN = data.token; /// now we can reuse the token
})
.catch(error => console.error("Error fetching CSRF token:", error));
})();
/// CSRF_TOKEN can be used
</script>
Submitting the form using fetch or other AJAX library
async function submitMyForm(event) {
event.preventDefault();
console.log("Submit called");
const form = event.target;
const formData = new FormData(form); // Collect form data
try {
const response = await fetch(form.action, {
method: "POST",
headers: {
"x-csrf-token": CSRF_TOKEN // set the token
},
body: formData // Send form data
});
const result = await response.json(); // Assuming server returns JSON
} catch (error) {
console.error("Error:", error);
}
}
Note that this is very suitable for AJAX based submission. For normal submission, you will need to append a query string csrf_token=xxx
to the POST URL.
Normal submit via query string
This is not preferred as it is less secure in general, but you can use if you have no choice.
<script>
async function submitMyForm(event) {
event.target.action = event.target.action + "?csrf_token=" + CSRF_TOKEN + "&other=aaa"; /// Append the CSRF token
return;
</script>
How can your code verify CSRF code?
Not required. All requests that reaches your code is CSRF validated and can be assumed to be safe.
Dependencies
~12–22MB
~302K SLoC