Session & CSRF
Sessions are encrypted cookie-based and auto-wired when app.key is configured.
How It Works
When config.app.key is set, Application::run() automatically creates two middleware layers:
- SessionLayer (outermost) — decrypts the session cookie, loads the
SessionHandle - CsrfLayer (inside SessionLayer) — validates CSRF tokens
Routes matching /api/* and /health are automatically CSRF-excepted.
Session Usage
use larastvel_core::session::SessionHandle;
async fn handler(mut session: SessionHandle) -> impl IntoResponse {
// Read
let count: Option<&str> = session.get("counter");
// Write
session.put("counter", count.unwrap_or("0"));
// Flash data
session.flash("status", "Saved!");
// Remove
session.forget("counter");
}CSRF Protection
CSRF tokens are validated via:
X-CSRF-TOKENheader (AJAX/SPA)X-XSRF-TOKENheader (Axios/Vite)_tokenform field (HTML forms)
Validation uses constant-time comparison via subtle::ConstantEq.
Origin Verification
Matching Laravel 13's PreventRequestForgery, state-changing requests are also checked against the Sec-Fetch-Site header. Requests from cross-site origins are rejected with a 419 "Origin mismatch." response, protecting against cross-site request forgery even when a token leaks.
CsrfLayer allows tuning the verification:
use larastvel_core::session::csrf::CsrfLayer;
// Default: cross-site requests must carry Sec-Fetch-Site: same-origin / same-site
let layer = CsrfLayer::new();
// Relax: trust all same-site requests (includes subdomains)
let relaxed = layer.allow_same_site(true);
// Strict: only requests with Sec-Fetch-Site: same-origin pass
let strict = relaxed.use_origin_only(true);Get CSRF Token in Templates
<form method="POST" action="/submit">
@csrf
<input name="title">
<button>Submit</button>
</form>The @csrf Blade directive renders <input type="hidden" name="_token" value="...">.
Session Configuration
Session behavior is controlled via SessionConfig:
SessionConfig {
cookie_name: "larastvel_session".into(),
secure: false, // true in production
http_only: true,
same_site: "lax".into(),
lifetime_minutes: 120,
}