` directive so the form includes a hidden `_token` field:\n\n```blade\n<form method=\"POST\" action=\"/profile\">\n \n <!-- fields -->\n</form>\n```\n\nFor PUT / PATCH / DELETE from a form, combine with `@method` and method spoofing middleware (see `methodSpoofing` from `jcc-express-mvc/Core`):\n\n```blade\n<form method=\"POST\" action=\"/posts/1\">\n \n \n</form>\n```\n\nThe directive outputs a hidden input named `_token` by default, matching `fieldName`.\n\n---\n\n## JavaScript and SPAs (Inertia, fetch, Axios)\n\nRead the token from the page (for example a meta tag rendered with `` or your framework’s equivalent) and send it on unsafe requests:\n\n```html\n<meta name=\"csrf-token\" content=\"\" />\n```\n\n```javascript\nconst token = document\n .querySelector('meta[name=\"csrf-token\"]')\n ?.getAttribute(\"content\");\nfetch(\"/profile\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-csrf-token\": token,\n },\n body: JSON.stringify({ name: \"Ada\" }),\n});\n```\n\nHeader name defaults to `x-csrf-token`; keep it aligned with `headerName` if you customize the middleware.\n\nFor 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.\n\n---\n\n## Failure behavior\n\nA missing or mismatched token throws `MissMatchTokenException` (mapped by the app error handler to an appropriate HTTP response). Treat 419-style handling in your `AppErrorHandler` or global exception layer if you customize statuses.\n\n---\n\n## Relationship to route middleware\n\n`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.\n","uri_path":"the-basics/csrf-protection","versionSlug":"2-x","versionName":"2.x","navCategories":[{"navKey":"getting-started","title":"Getting Started","slug":"getting-started","items":[{"title":"Introduction","slug":"getting-started/introduction"},{"title":"Installation","slug":"getting-started/installation"},{"title":"Configuration","slug":"getting-started/configuration"},{"title":"Directory structure","slug":"getting-started/directory-structure"},{"title":"Frontend","slug":"getting-started/frontend"},{"title":"Deployment","slug":"getting-started/deployment"}]},{"navKey":"architecture-concepts","title":"Architecture Concepts","slug":"architecture-concepts","items":[{"title":"Request Lifecycle","slug":"architecture-concepts/request-lifecycle"},{"title":"Service Container","slug":"architecture-concepts/service-container"},{"title":"Service Provider","slug":"architecture-concepts/service-provider"}]},{"navKey":"the-basics","title":"The Basics","slug":"the-basics","items":[{"title":"Routing","slug":"the-basics/routing"},{"title":"Middleware","slug":"the-basics/middleware"},{"title":"CSRF protection","slug":"the-basics/csrf-protection"},{"title":"Controllers","slug":"the-basics/controllers"},{"title":"Request","slug":"the-basics/request"},{"title":"Response","slug":"the-basics/response"},{"title":"Views","slug":"the-basics/views"},{"title":"JCC Blade","slug":"the-basics/jcc-blade"},{"title":"Asset Bundling","slug":"the-basics/asset-bundling"},{"title":"Session","slug":"the-basics/session"},{"title":"Validation","slug":"the-basics/validation"},{"title":"Error Handling","slug":"the-basics/error-handling"}]},{"navKey":"digging-deeper","title":"Digging Deeper","slug":"digging-deeper","items":[{"title":"ArtisanNode","slug":"digging-deeper/artisan-node"},{"title":"Broadcasting","slug":"digging-deeper/broadcasting"},{"title":"Cache","slug":"digging-deeper/cache"},{"title":"Events","slug":"digging-deeper/events"},{"title":"File Storage","slug":"digging-deeper/file-storage"},{"title":"Helpers","slug":"digging-deeper/helpers"},{"title":"HTTP Client","slug":"digging-deeper/http-client"},{"title":"Mail","slug":"digging-deeper/mail"},{"title":"Queues","slug":"digging-deeper/queues"},{"title":"Rate Limiting","slug":"digging-deeper/rate-limiting"},{"title":"Strings","slug":"digging-deeper/strings"}]},{"navKey":"security","title":"Security","slug":"security","items":[{"title":"Authentication","slug":"security/authentication"},{"title":"Authorization","slug":"security/authorization"},{"title":"Email Verification","slug":"security/email-verification"},{"title":"Encryption","slug":"security/encryption"},{"title":"Hashing","slug":"security/hashing"}]},{"navKey":"database","title":"Database","slug":"database","items":[{"title":"Introduction","slug":"database/introduction"},{"title":"Query Builder","slug":"database/query-builder"},{"title":"Migrations","slug":"database/migrations"},{"title":"Seeding","slug":"database/seeding"},{"title":"Transactions","slug":"database/transactions"},{"title":"Mongoose","slug":"database/mongoose"},{"title":"Sequelize","slug":"database/sequelize"}]},{"navKey":"jcc-eloquent-orm","title":"JCC Eloquent ORM","slug":"jcc-eloquent-orm","items":[{"title":"Introduction","slug":"jcc-eloquent-orm/introduction"},{"title":"Defining Models","slug":"jcc-eloquent-orm/defining-models"},{"title":"Retrieving Models","slug":"jcc-eloquent-orm/retrieving-models"},{"title":"Relationships","slug":"jcc-eloquent-orm/relationships"},{"title":"Pagination","slug":"jcc-eloquent-orm/pagination"},{"title":"Mutators","slug":"jcc-eloquent-orm/mutators"},{"title":"Scopes","slug":"jcc-eloquent-orm/scopes"},{"title":"Soft Deleting","slug":"jcc-eloquent-orm/soft-deleting"},{"title":"Events & Observers","slug":"jcc-eloquent-orm/events-and-observers"}]},{"navKey":"testing","title":"Testing","slug":"testing","items":[{"title":"Introduction","slug":"testing/introduction"},{"title":"Testing Overview","slug":"testing/testing-overview"},{"title":"Unit Testing","slug":"testing/unit-testing"},{"title":"Feature Testing","slug":"testing/feature-testing"},{"title":"Database Testing","slug":"testing/database-testing"}]},{"navKey":"packages","title":"Packages","slug":"packages","items":[{"title":"JWT","slug":"packages/jwt"},{"title":"Socialite","slug":"packages/socialite"}]}],"prevPage":{"title":"Middleware","uri_path":"the-basics/middleware"},"nextPage":{"title":"Controllers","uri_path":"the-basics/controllers"},"searchPages":[{"title":"Introduction","uri_path":"getting-started/introduction","excerpt":"Getting Started — Introduction"},{"title":"Installation","uri_path":"getting-started/installation","excerpt":"Getting Started — Installation"},{"title":"Configuration","uri_path":"getting-started/configuration","excerpt":"Getting Started — Configuration"},{"title":"Directory structure","uri_path":"getting-started/directory-structure","excerpt":"Getting Started — Directory structure"},{"title":"Frontend","uri_path":"getting-started/frontend","excerpt":"Getting Started — Frontend"},{"title":"Deployment","uri_path":"getting-started/deployment","excerpt":"Getting Started — Deployment"},{"title":"Request Lifecycle","uri_path":"architecture-concepts/request-lifecycle","excerpt":"Architecture Concepts — Request Lifecycle"},{"title":"Service Container","uri_path":"architecture-concepts/service-container","excerpt":"Architecture Concepts — Service Container"},{"title":"Service Provider","uri_path":"architecture-concepts/service-provider","excerpt":"Architecture Concepts — Service Provider"},{"title":"Routing","uri_path":"the-basics/routing","excerpt":"The Basics — Routing"},{"title":"Middleware","uri_path":"the-basics/middleware","excerpt":"The Basics — Middleware"},{"title":"CSRF protection","uri_path":"the-basics/csrf-protection","excerpt":"The Basics — CSRF protection"},{"title":"Controllers","uri_path":"the-basics/controllers","excerpt":"The Basics — Controllers"},{"title":"Request","uri_path":"the-basics/request","excerpt":"The Basics — Request"},{"title":"Response","uri_path":"the-basics/response","excerpt":"The Basics — Response"},{"title":"Views","uri_path":"the-basics/views","excerpt":"The Basics — Views"},{"title":"JCC Blade","uri_path":"the-basics/jcc-blade","excerpt":"The Basics — JCC Blade"},{"title":"Asset Bundling","uri_path":"the-basics/asset-bundling","excerpt":"The Basics — Asset Bundling"},{"title":"Session","uri_path":"the-basics/session","excerpt":"The Basics — Session"},{"title":"Validation","uri_path":"the-basics/validation","excerpt":"The Basics — Validation"},{"title":"Error Handling","uri_path":"the-basics/error-handling","excerpt":"The Basics — Error Handling"},{"title":"ArtisanNode","uri_path":"digging-deeper/artisan-node","excerpt":"Digging Deeper — ArtisanNode"},{"title":"Broadcasting","uri_path":"digging-deeper/broadcasting","excerpt":"Digging Deeper — Broadcasting"},{"title":"Cache","uri_path":"digging-deeper/cache","excerpt":"Digging Deeper — Cache"},{"title":"Events","uri_path":"digging-deeper/events","excerpt":"Digging Deeper — Events"},{"title":"File Storage","uri_path":"digging-deeper/file-storage","excerpt":"Digging Deeper — File Storage"},{"title":"Helpers","uri_path":"digging-deeper/helpers","excerpt":"Digging Deeper — Helpers"},{"title":"HTTP Client","uri_path":"digging-deeper/http-client","excerpt":"Digging Deeper — HTTP Client"},{"title":"Mail","uri_path":"digging-deeper/mail","excerpt":"Digging Deeper — Mail"},{"title":"Queues","uri_path":"digging-deeper/queues","excerpt":"Digging Deeper — Queues"},{"title":"Rate Limiting","uri_path":"digging-deeper/rate-limiting","excerpt":"Digging Deeper — Rate Limiting"},{"title":"Strings","uri_path":"digging-deeper/strings","excerpt":"Digging Deeper — Strings"},{"title":"Authentication","uri_path":"security/authentication","excerpt":"Security — Authentication"},{"title":"Authorization","uri_path":"security/authorization","excerpt":"Security — Authorization"},{"title":"Email Verification","uri_path":"security/email-verification","excerpt":"Security — Email Verification"},{"title":"Encryption","uri_path":"security/encryption","excerpt":"Security — Encryption"},{"title":"Hashing","uri_path":"security/hashing","excerpt":"Security — Hashing"},{"title":"Introduction","uri_path":"database/introduction","excerpt":"Database — Introduction"},{"title":"Query Builder","uri_path":"database/query-builder","excerpt":"Database — Query Builder"},{"title":"Migrations","uri_path":"database/migrations","excerpt":"Database — Migrations"},{"title":"Seeding","uri_path":"database/seeding","excerpt":"Database — Seeding"},{"title":"Transactions","uri_path":"database/transactions","excerpt":"Database — Transactions"},{"title":"Mongoose","uri_path":"database/mongoose","excerpt":"Database — Mongoose"},{"title":"Sequelize","uri_path":"database/sequelize","excerpt":"Database — Sequelize"},{"title":"Introduction","uri_path":"jcc-eloquent-orm/introduction","excerpt":"JCC Eloquent ORM — Introduction"},{"title":"Defining Models","uri_path":"jcc-eloquent-orm/defining-models","excerpt":"JCC Eloquent ORM — Defining Models"},{"title":"Retrieving Models","uri_path":"jcc-eloquent-orm/retrieving-models","excerpt":"JCC Eloquent ORM — Retrieving Models"},{"title":"Relationships","uri_path":"jcc-eloquent-orm/relationships","excerpt":"JCC Eloquent ORM — Relationships"},{"title":"Pagination","uri_path":"jcc-eloquent-orm/pagination","excerpt":"JCC Eloquent ORM — Pagination"},{"title":"Mutators","uri_path":"jcc-eloquent-orm/mutators","excerpt":"JCC Eloquent ORM — Mutators"},{"title":"Scopes","uri_path":"jcc-eloquent-orm/scopes","excerpt":"JCC Eloquent ORM — Scopes"},{"title":"Soft Deleting","uri_path":"jcc-eloquent-orm/soft-deleting","excerpt":"JCC Eloquent ORM — Soft Deleting"},{"title":"Events & Observers","uri_path":"jcc-eloquent-orm/events-and-observers","excerpt":"JCC Eloquent ORM — Events & Observers"},{"title":"Introduction","uri_path":"testing/introduction","excerpt":"Testing — Introduction"},{"title":"Testing Overview","uri_path":"testing/testing-overview","excerpt":"Testing — Testing Overview"},{"title":"Unit Testing","uri_path":"testing/unit-testing","excerpt":"Testing — Unit Testing"},{"title":"Feature Testing","uri_path":"testing/feature-testing","excerpt":"Testing — Feature Testing"},{"title":"Database Testing","uri_path":"testing/database-testing","excerpt":"Testing — Database Testing"},{"title":"JWT","uri_path":"packages/jwt","excerpt":"Packages — JWT"},{"title":"Socialite","uri_path":"packages/socialite","excerpt":"Packages — Socialite"}]},"url":"/docs/2-x/the-basics/csrf-protection","version":"1"}">
JCC Express

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

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

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 directive so the form includes a hidden _token field:

blade

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

blade

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 or your framework’s equivalent) and send it on unsafe requests:

HTML
JavaScript

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 app error handler to an appropriate HTTP response). Treat 419-style handling in your AppErrorHandler or global exception layer if you customize statuses.


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.