Schematra-CSRF
Cross-Site Request Forgery (CSRF) protection middleware for Schematra web applications.
Description
Schematra-CSRF provides CSRF protection for Schematra applications through middleware that automatically validates tokens on unsafe HTTP methods. It generates random tokens, stores them in sessions, and validates them on form submissions and API requests to help prevent CSRF attacks.
Requirements
- CHICKEN Scheme 5.0 or later
- Dependencies: schematra, schematra-session, spiffy, intarweb, base64
- Sessions must be enabled (schematra-session middleware required)
Installation
chicken-install schematra-csrf
Basic Usage
(import schematra schematra-csrf schematra-session chiccup) ;; Enable sessions (required for CSRF) (use-middleware! (session-middleware "secret-key")) ;; Enable CSRF protection (use-middleware! (csrf-middleware)) ;; Form with CSRF protection (get ("/form") (ccup->html `[form (@ (method "POST") (action "/submit")) ,(chiccup-csrf-hidden-input) ; Automatic hidden input [input (@ (type "text") (name "message"))] [button "Submit"]])) (post ("/submit") "Form processed securely!") (schematra-install) (schematra-start)
API
Middleware
[procedure] (csrf-middleware)Creates CSRF protection middleware for Schematra applications. This middleware automatically protects against Cross-Site Request Forgery attacks by validating CSRF tokens on unsafe HTTP methods.
Behavior:
- Safe methods (GET, HEAD, OPTIONS, TRACE) pass through without validation
- Unsafe methods (POST, PUT, DELETE, PATCH) require a valid CSRF token
- Missing or invalid tokens result in a 403 Forbidden response
The middleware looks for CSRF tokens in two places:
- Form data using the field name from csrf-form-field parameter
- HTTP header X-CSRF-Token
;; Enable CSRF protection globally (use-middleware! (csrf-middleware)) ;; Custom configuration (csrf-form-field "authenticity_token") (csrf-token-key "my-csrf-key") (use-middleware! (csrf-middleware))
Token Management
[procedure] (csrf-get-token)Retrieves or generates a CSRF token for the current session. This function first checks if a CSRF token already exists in the session. If found, it returns the existing token. If not found, it generates a new cryptographically secure token, stores it in the session, and returns it.
Returns: A base64-encoded string containing the CSRF token
;; Get token for use in forms (let ((token (csrf-get-token))) `[input (@ (type "hidden") (name ,(symbol->string (csrf-form-field))) (value ,token))]) ;; Get token for AJAX requests (get ("/api/token") (csrf-get-token))[procedure] (chiccup-csrf-hidden-input)
Generates a Chiccup-formatted hidden input element containing the CSRF token. This is a convenience function that combines token generation with HTML generation.
Returns: A Chiccup list representing an HTML hidden input element
;; Use in forms `[form (@ (method "POST") (action "/submit")) ,(chiccup-csrf-hidden-input) ; Automatic CSRF protection [input (@ (type "text") (name "username"))] [button "Submit"]] ;; Equivalent manual version `[form (@ (method "POST") (action "/submit")) [input (@ (type "hidden") (name ,(symbol->string (csrf-form-field))) (value ,(csrf-get-token)))] [input (@ (type "text") (name "username"))] [button "Submit"]]
Configuration Parameters
[parameter] (csrf-token-key [key])Parameter that defines the session key used to store CSRF tokens.
Parameters:
- key: string - The session key name (default: "csrf-token")
;; Use default key (csrf-token-key) ; => "csrf-token" ;; Customize the session key (csrf-token-key "my-csrf-key")[parameter] (csrf-form-field [field-name])
Parameter that defines the HTML form field name used for CSRF tokens.
Parameters:
- field-name: symbol - The form field name (default: '_csrf_token')
;; Use default field name (csrf-form-field) ; => '_csrf_token' ;; Customize the form field name (csrf-form-field 'authenticity_token)
AJAX Integration
For AJAX requests, include the CSRF token in the X-CSRF-Token header:
// Get token from a dedicated endpoint fetch('/api/csrf-token') .then(response => response.text()) .then(token => { // Use token in subsequent requests fetch('/api/data', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': token }, body: JSON.stringify({data: 'example'}) }); });
Provide a token endpoint:
(get ("/api/csrf-token")
(csrf-get-token))
Complete Example
(import schematra schematra-csrf schematra-session chiccup) ;; Configure CSRF (optional - uses sensible defaults) (csrf-form-field 'authenticity_token) (csrf-token-key "app-csrf-token") ;; Install middleware (order matters!) (use-middleware! (session-middleware "your-secret-key")) (use-middleware! (csrf-middleware)) ;; Home page with protected form (get ("/") (ccup->html `[html [head [title "CSRF Demo"]] [body [h1 "CSRF Protected Form"] [form (@ (method "POST") (action "/submit")) ,(chiccup-csrf-hidden-input) [p [label "Message: "] [input (@ (type "text") (name "message") (required))]] [p [button (@ (type "submit")) "Send Message"]]] [h2 "AJAX Example"] [button (@ (onclick "sendAjaxRequest()")) "Send AJAX"] [script " function sendAjaxRequest() { fetch('/api/csrf-token') .then(response => response.text()) .then(token => { return fetch('/api/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': token }, body: JSON.stringify({message: 'Hello from AJAX'}) }); }) .then(response => response.text()) .then(data => alert('Response: ' + data)); }"]]])) ;; Form submission handler (post ("/submit") (let ((message (alist-ref 'message (current-params)))) (ccup->html `[html [body [h1 "Message Received"] [p ,(string-append "You said: " (or message ""))] [a (@ (href "/")) "Back"]]]))) ;; CSRF token endpoint for AJAX (get ("/api/csrf-token") (csrf-get-token)) ;; AJAX submission handler (post ("/api/submit") (let ((message (alist-ref 'message (current-params)))) (string-append "AJAX message received: " (or message "")))) (schematra-install) (schematra-start)
Security Notes
- CSRF tokens are generated using random bytes (via CHICKEN's random-bytes function)
- Tokens are stored in server-side sessions, not in cookies or client storage
- The middleware automatically handles token lifecycle (generation, validation, storage)
- Safe HTTP methods (GET, HEAD, OPTIONS, TRACE) are exempt from CSRF validation
- Both form data and HTTP headers are checked for tokens, providing flexibility for different client types
License
Copyright © 2025 Rolando Abarca. Released under the BSD-3-Clause license.
Repository
Part of the Schematra project.