CSRF Exemptions
Pubvana applies CSRF token verification globally to all POST, PUT, DELETE, and PATCH requests. This breaks webhook endpoints and external API callbacks that POST to your plugin without a CSRF token.
getCsrfExemptions()
Declare exempt routes by returning their URI patterns from getCsrfExemptions() in Plugin.php:
public function getCsrfExemptions(): array
{
return [
'dstore/webhooks/stripe',
'dstore/webhooks/paypal',
'dstore/webhooks/*',
];
}
Patterns are injected into CI4's CSRF filter except list during PluginManager::boot(). Matched against the request URI without the leading slash.
Pattern Syntax
| Pattern | Matches |
|---|---|
myplugin/webhooks/stripe | Exact path only |
myplugin/webhooks/* | Any single segment after webhooks/ |
myplugin/api/* | Any path starting with myplugin/api/ |
Use the most specific pattern possible. Never exempt myplugin/* — that would expose all your routes to CSRF-free POSTs.
When to Use CSRF Exemptions
Use for: payment gateway webhooks, third-party service callbacks, mobile API endpoints using token auth.
Do NOT use for: admin forms, public comment forms, or any form submitted by your own JavaScript (include the CSRF token in the AJAX request instead).
Including CSRF in AJAX Requests
const csrfName = document.querySelector('meta[name="csrf-token-name"]').content;
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
fetch('/myplugin/admin/items', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
[csrfName]: csrfToken,
},
body: JSON.stringify({ title: 'New Item' }),
});
Webhook Security
Exempting a route from CSRF does not mean it is unprotected. Always verify webhook authenticity using the provider's signing secret:
$payload = $this->request->getBody();
$sigHeader = $this->request->getHeaderLine('Stripe-Signature');
$secret = setting('DStore.stripeWebhookSecret');
try {
$event = StripeWebhook::constructEvent($payload, $sigHeader, $secret);
} catch (StripeExceptionSignatureVerificationException $e) {
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid signature']);
}