Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Csrf Laravel Package

slim/csrf

Slim CSRF provides CSRF protection for Slim 4 apps via PSR-15 middleware. It generates and validates tokens, supports session-based or custom storage, and can inject hidden token fields into forms and views.

View on GitHub
Deep Wiki
Context7

Getting Started

What you're getting: a PSR-15 middleware that issues a per-request CSRF token, puts it into the request (as attributes and via a Guard instance), validates the submitted token on state-changing requests, and hands off to a failure callable when validation fails. It's maintained under the slimphp org, MIT-licensed, and works with Slim 4 (and any PSR-15 stack) — the slim/slim dependency isn't required, only PSR-7/PSR-15 interfaces.

Minimal wiring (Slim 4):

// public/index.php
$app = AppFactory::create();
$app->addBodyParsingMiddleware();
$app->addRoutingMiddleware();

$app->add(new \Slim\Csrf\Guard());   // add *after* routing middleware so it wraps routes

Emit the token in a form: every non-GET form needs both fields, not one:

<input type="hidden" name="<?= $csrf->getTokenName() ?>"  value="<?= $csrf->getTokenValue() ?>">

or, from inside a route handler, read the attributes the middleware sets:

$request->getAttribute('csrf_name');   // e.g. "csrf_name"
$request->getAttribute('csrf_value');  // the token string

That's it — you write zero validation code. The middleware inspects the incoming request and rejects it before your handler runs. A POST to that form with the fields intact will pass; a POST without them (or with a stale token) will hit the failure path.

First use case: a login form or a contact form. Add the middleware globally, add the two hidden inputs to the form, submit it, then delete one of the hidden inputs and confirm you get rejected. That 30-second loop is the whole mental model of this package.

Where to look first: the README's middleware-registration snippet, then setFailureCallable() — you will almost certainly override the default failure behaviour in your first hour.

Implementation Patterns

Register once, inject everywhere. Don't new Guard() in more than one place, or you'll hand out two different token names/values in the same response. Bind it in your container and add the container-resolved instance:

// app/dependencies.php
$container->set(\Slim\Csrf\Guard::class, fn () => new \Slim\Csrf\Guard());

// public/index.php
$app->add($container->get(\Slim\Csrf\Guard::class));

Then pass $csrf into your view layer (Twig global, Plates $data, Blade share) so templates can call getTokenName()/getTokenValue() without threading the request through.

Template partial — do this on day one. One reusable snippet, included by every form, prevents the single most common bug (forgetting a field in one form out of twelve):

{# templates/_csrf.twig #}
<input type="hidden" name="{{ csrf.getTokenName() }}" value="{{ csrf.getTokenValue() }}">

If you're on a version that populates request attributes, a Slim Request object in the view works equally well.

Scope it to forms, not the world. Most apps add the middleware globally and then have to exempt webhooks, JSON APIs, and health checks. Two approaches:

// Group-level: only admin + public forms get protection
$app->group('/admin', function (RouteCollectorProxy $group) { ... })
    ->add($container->get(\Slim\Csrf\Guard::class));

// Or global + path check in the failure callable

Group-level is cleaner in Slim 4 because $app->add() middleware runs before routing, so inside a global failure callable you don't yet have a RouteContext to inspect.

Custom failure callable — always. The default behaviour (throwing a RuntimeException / TokenMismatchException-style error) becomes a 500 in production. Replace it with something user-facing:

$csrf->setFailureCallable(function ($request, $response, $next) {
    // redirect back with a flash message, or render a 400 page
    return $response->withStatus(400);
});

Log the rejection (path, IP, authenticated user) — a spike in CSRF failures is either a broken deploy or an actual attack, and you want to know which.

Persistent token mode for SPA-ish flows. By default the token is short-lived; if users keep forms open (or you have a modal-driven UI), set setPersistentTokenMode(true) so a token survives longer than a single render/request cycle. Pair it with the storage limit / strength setters if your version exposes them, and confirm the behaviour in a test rather than assuming.

AJAX / fetch. The middleware validates the submitted body by default. If your front end posts JSON, you have two options: post a FormData payload containing the two token fields (simplest, zero package changes), or read the token name/value into a <meta> tag server-side, send it as a header, and wrap the Guard's request with a middleware that copies the header into the parsed body before the Guard runs. Pick one and write it down — mixed conventions across a codebase is how you get intermittent 400s.

Testing. Functional tests that POST must first GET the page and scrape the token (or, better, resolve the Guard from the container and read getTokenName()/getTokenValue() directly). For unit tests of unrelated handlers, construct the app without the middleware rather than fighting tokens. If you have a test bootstrap, put a helper in it — you'll use it in every CRUD test.

Gotchas and Tips

Middleware order is the #1 support ticket. In Slim 4, $app->add() is LIFO and runs before routing. Add the CSRF middleware after addRoutingMiddleware() so it sits inside the routing layer, and be deliberate about whether it runs before or after session/body-parsing middleware — the Guard needs a parsed body to see form fields.

Version drift between 0.x and 1.x is real. The older line exposes Guard::__invoke($request, $response, $next) (Slim 3-style double-pass); the newer releases are pure PSR-15 (process($request, $handler)), and the FQCN/registration may differ. Snippets from blog posts and Stack Overflow are overwhelmingly 0.x-era. Check composer show slim/csrf and the current README before pasting anything with $next in it.

Token lifetime and multiple tabs. If the token is regenerated on each request, opening the same form in two tabs means the second render invalidates the first submit. Persistent token mode exists for exactly this. Test it explicitly — "it works until you have two tabs open" is an expensive bug to find in production.

A stale tab yields a confusing failure. Users leave checkout open for an hour, come back, hit submit, and get a generic error with all their data gone. The failure callable should preserve the POSTed data (flash it into the session) and re-render the form with a friendly message and a fresh token.

Token mismatch ≠ attack. Common benign causes: missing hidden field in a newly added form, a reverse proxy stripping cookies, a page served from a full-page cache (CDN/Cloudflare/Varnish) that cached someone else's token, or a load balancer routing to a node with different in-memory state. If you use full-page caching, exclude form pages or switch to an ESI/edge-side token.

Only POST/PUT/PATCH/DELETE are meaningful. CSRF protection is pointless if you have a GET /delete/42 route. Audit for state-changing GETs before relying on this middleware; it can't save you from a bad route design.

Don't confuse it with session security. This is not a replacement for SameSite=Lax cookies, HttpOnly, or HTTPS. Layer them. Conversely, if you already have SameSite=Strict session cookies, the marginal value of the middleware is small but non-zero — keep it for defence in depth, especially on login forms.

Storage is an extension point. If you need tokens to survive across a cluster, want Redis-backed storage, or need to integrate with an existing session store, implement the storage interface the package ships and inject it. That's the cleanest place to solve "our tokens don't work behind the load balancer" without forking the Guard.

Never log or expose the token value. It ends up in HTML by necessity, but don't put it in error logs, analytics payloads, or APM traces. Same for getTokenName() in anything user-visible if your version randomises it.

Long-running runtimes (RoadRunner, Swoole, FrankenPHP worker mode). The Guard may hold per-request state in memory. Verify a fresh token is issued per request and that nothing leaks between concurrent requests — reset the instance from the container per request rather than treating it as a singleton across the worker lifetime.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core