Secure server actions
Every #[server] function is a public API. Validate input, return Result<T>, and use middleware for auth. Live code: todo example.
1. Return Result (fail closed)
Do not silently ignore bad input. Return Result<T, ResumaError> — the framework maps errors to HTTP 401/403/422.
#[server]
async fn add_todo(title: String, req: &FlowRequest) -> Result<Vec<Todo>> {
let title = security::normalize_title(&title)?;
security::can_add_todo(store.len())?;
todo_store::add(title, req)
}2. DTO validation (ValidationPipe pattern)
pub struct AddTodoInput { pub title: String }
impl AddTodoInput {
pub fn into_title(self) -> Result<String> {
security::normalize_title(&self.title)
}
}3. FlowRequest for auth & audit
Add req: &FlowRequest as the last parameter.
let uid = req.user_id().ok_or(ResumaError::Unauthorized)?;
security::assert_owner(&todo.owner_id, req)?;4. Action middleware (ResumaApp guard)
pub fn install() {
set_action_middleware(action_pipeline);
}
fn action_pipeline(req: FlowRequest) -> ... {
let req = attach_session(req)?; // guard
audit_action(&req); // interceptor
Ok(req)
}For Flow multi-page apps, use #[middleware] instead — see Auth middleware.
5. CSRF is automatic
Runtime sends X-Resuma-CSRF. Forms include hidden _csrf via form().
6. Handle errors in the UI
try {
const next = await __resuma.action("add_todo", [title]);
state.todos.set(next);
} catch (e) {
state.ui.update(s => { s.status = "Forbidden or rate limited"; });
}