Sessions and CSRF
Sessions keep values per visitor on the server; the cookie carries only a random ID. csrf() makes sure a form post really came from your own page.
Turning them on#
Add both to the group that serves pages. JSON APIs usually stay outside it.
use rustclamp::config::Config;
use rustclamp::web::{Router, Sessions, csrf, redirect};
pub fn routes(router: Router, config: &Config) -> Router {
// SESSION_MINUTES (default 120) and SESSION_SECURE (on in production)
let sessions = Sessions::from_config(config);
router.group(|site| {
site.middleware(sessions.middleware())
.middleware(csrf()) // after Sessions: it needs the session
.get("/contact", contact_form)
.post("/contact", |request| {
let name = request.form("name").unwrap_or_default();
if let Some(session) = request.session() {
session.put("flash", &format!("Thanks, {name}!"));
}
redirect("/contact")
})
})
}
Reading and writing values#
| Call | Does |
|---|---|
session.get("key") | Reads a value |
session.put("key", "value") | Stores a value |
session.take("flash") | Reads and removes it: a one-time flash message |
session.csrf_token() | The session's CSRF token, created on first use |
session.regenerate() | A new session ID and CSRF token, keeping the values. Call it on login, logout and any change of privilege, so an ID someone planted or saw earlier stops working |
session.csrf_field() | A ready hidden <input name="_token"> for forms |
Values are strings. The cookie is named clamp_session (COOKIE), and every response refreshes it.
CSRF in forms#
csrf() refuses POST, PUT, PATCH and DELETE requests whose _token form field or X-CSRF-Token header does not match the session, with 419 Page Expired. Put the field in every form:
pub fn contact_form(request: &Request) -> Response {
// Passes csrf, flash, errors and old input to the view.
request.render("contact", &[])
}
<form method="post" action="/contact">
{!! csrf !!}
<input name="name" value="{{ old.name }}" required>
<button>Send</button>
</form>
@if(flash)<p>{{ flash }}</p>@endif
For fetch calls, send the token in the X-CSRF-Token header instead.
In production#
- HTTPS only: with
Sessions::from_configthe cookie isSecurewheneverAPP_ENV=production, and the app refuses to start ifSESSION_SECURE=falsethere.Sessions::new(…).secure(true)does the same by hand. - In memory: sessions live in the server process, so a restart signs everyone out. Keep durable state in a database.