JCC Express

The Basics

CSRF protection

Introduction

Cross-site request forgery (CSRF) tricks a logged-in user’s browser into submitting a state-changing request the user did not intend. JCC Express MVC mitigates this with a session-bound token: each session gets a random value; unsafe requests must repeat that value in the body or in a header so only your pages and scripts can succeed.

The csrf() middleware (from jcc-express-mvc/Core) stores the token in the session, exposes it to templates and code, and verifies it on POST, PUT, PATCH, and DELETE when verification applies.

It must run after express-session (and cookie parsing). The default application stack registers session before your HTTP kernel’s middlewares array, so placing csrf() in app/Http/kernel.ts is correct. Session usage is covered in Session.md.


Enabling CSRF

Add the factory to your kernel’s global middleware:

TypeScript
import { csrf, methodSpoofing } from "jcc-express-mvc/Core";

export class Kernel {
  public middlewares = [
    csrf(),
    methodSpoofing(),
    // …
  ];
}

If req.session is missing, csrf() logs a warning and skips verification so the app does not hard-fail; in production you should ensure session middleware is present.


When verification runs

  • Safe methods (GET, HEAD, OPTIONS, …) — no check; the token is still created or refreshed in session and exposed for the response.
  • Unsafe methods (POST, PUT, PATCH, DELETE) — the submitted token must match the session token, unless a skip rule applies (below).

Skip rules

API prefix

By default skipApi is true: any path whose req.path starts with /api skips CSRF. That matches a common split where route/api.ts is mounted under /api in bootstrap/app.ts.

State-changing JSON APIs under another prefix should either use except, set skipApi: false and send the token (or use other API auth such as tokens), or mount those routes under /api.

Exclusions

Pass except to skip verification for specific paths (exact match or prefix with a trailing slash rule, or RegExp):

TypeScript
csrf({
  except: ["/webhooks/stripe", /^\/callbacks\//],
});

String rules: path equals the string, or path starts with string + "/" (see CsrfMiddleware implementation).


CsrfOptions

  • except — Default []. Paths (string or RegExp) that skip verification on unsafe methods.
  • sessionKey — Default "_csrf_token". Session key used to store the token.
  • fieldName — Default "_token". Form field name read from req.body.
  • headerName — Default "x-csrf-token". Header checked; req.headers is also read with a lowercased key.
  • tokenLength — Default 32. Random byte length before hex encoding (the hex string length is twice this).
  • skipApi — Default true. When req.path starts with /api, verification is skipped.

Exposing the token

After csrf() runs for a request:

  • res.locals._token — use in Blade-style templates (hidden inputs, meta tags).
  • req.csrfToken() — function returning the current token (same value as res.locals._token).

HTML forms (Blade / server templates)

Use the @csrf directive so the form includes a hidden _token field:

blade
<form method="POST" action="/profile">
  @csrf
  <!-- fields -->
</form>

For PUT / PATCH / DELETE from a form, combine with @method and method spoofing middleware (see methodSpoofing from jcc-express-mvc/Core):

blade
<form method="POST" action="/posts/1">
  @csrf
  @method('PUT')
</form>

The directive outputs a hidden input named _token by default, matching fieldName.


JavaScript and SPAs (Inertia, fetch, Axios)

Read the token from the page (for example a meta tag rendered with {{ _token }} or your framework’s equivalent) and send it on unsafe requests:

HTML
<meta name="csrf-token" content="{{ _token }}" />
JavaScript
const token = document
  .querySelector('meta[name="csrf-token"]')
  ?.getAttribute("content");
fetch("/profile", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-csrf-token": token,
  },
  body: JSON.stringify({ name: "Ada" }),
});

Header name defaults to x-csrf-token; keep it aligned with headerName if you customize the middleware.

For Inertia, the same idea applies: ensure the shared view or root template exposes the token and that client-side navigations or router.post calls include the header or body field your middleware expects.


Failure behavior

A missing or mismatched token throws MissMatchTokenException (mapped by the framework error handler to an appropriate HTTP response). Customize 419-style handling in your exception layer if needed.


Relationship to route middleware

csrf() is normally global (kernel middlewares), not per-route. Use except / skipApi to narrow scope. Route-level auth or guest is separate; CSRF protects session-cookie-authenticated browser flows regardless of auth middleware order, as long as csrf() runs on those requests.