Cross-Site Request Forgery (CSRF) is a malicious exploit where unauthorized commands are performed on behalf of an authenticated user. Anchor automatically protects your application from CSRF attacks.
Anchor generates a unique CSRF token for each active user session. This token is verified on every state-changing request (POST, PUT, PATCH, DELETE) to ensure the authenticated user is actually making the request.
CSRF protection is configured in App/Config/default.php:
'csrf' => [
'enable' => true,
'persist' => false, // Regenerate token after each request
'origin_check' => true, // Verify IP and user agent
'honeypot' => true, // Enable honeypot field
'routes' => [
'exclude' => [], // Routes to exclude from CSRF protection
],
],Use $this->csrf() in view templates:
<form method="POST" action="<?php echo url('account/update'); ?>">
<?php echo $this->csrf(); ?>
<input type="text" name="name">
<button type="submit">Update</button>
</form>The $this->csrf() method generates:
<input type="hidden" name="csrf_token" value="base64_encoded_token_here" />If honeypot is enabled, it also generates:
<input type="hidden" name="top_yenoh" value="" />For forms with method spoofing (PUT, PATCH, DELETE):
<form method="POST" action="<?php echo url('posts/delete'); ?>">
<?php echo $this->importantFormFields('DELETE'); ?>
<button type="submit">Delete Post</button>
</form>This generates:
- CSRF token
- Method field (
_method) - Callback route field
- Honeypot (if enabled)
In PHP
Use the csrf_token() helper:
$token = csrf_token();In Views
<meta name="csrf-token" content="<?php echo csrf_token(); ?>">In JavaScript (AJAX)
// Get token from meta tag
const token = document.querySelector('meta[name="csrf-token"]').content;
// Include in AJAX requests
fetch("/api/endpoint", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": token,
},
body: JSON.stringify(data),
});Or include in form data:
const formData = new FormData();
formData.append("csrf_token", token);
formData.append("name", "John");
fetch("/api/endpoint", {
method: "POST",
body: formData,
});- Token Generation: Created when session starts, stored in session
- Token Embedding: Added to forms via
$this->csrf() - Token Submission: Sent with POST/PUT/PATCH/DELETE requests
- Token Validation: Middleware validates token matches session using constant-time comparisons (
hash_equals()) to prevent timing attacks. - Token Regeneration: Optionally regenerated after each request (if
persistis false)
CSRF tokens expire based on session timeout:
// In App/Config/session.php
'timeout' => 3600, // 1 hourWhen origin_check is enabled, the token is bound to:
- User's IP address
- User's user agent
The origin hash is also verified using constant-time comparison for maximum security.
This provides additional security but may cause issues if users switch networks or browsers.
The honeypot is a hidden field that should remain empty. Bots typically fill all fields, triggering detection:
// In request validation
if ($this->request->isBot()) {
// Request blocked - honeypot was filled
}Configure excluded routes in App/Config/default.php:
'csrf' => [
'routes' => [
'exclude' => [
'api/*',
'webhooks/*',
],
],
],Check CSRF token manually:
use Helpers\Http\Request;
public function store(Request $request)
{
if (!$request->isSecurityValid()) {
// CSRF validation failed
return $this->response->status(403)->body('Invalid CSRF token');
}
// Process request
}<form method="POST" action="<?php echo url('account/profile'); ?>">
<?php echo $this->csrf(); ?>
<div>
<label>Name</label>
<input type="text" name="name" value="<?php echo $this->escape($user->name); ?>">
</div>
<div>
<label>Email</label>
<input type="email" name="email" value="<?php echo $this->escape($user->email); ?>">
</div>
<button type="submit">Update Profile</button>
</form><form method="POST" action="<?php echo url('posts/' . $post->id); ?>">
<?php echo $this->importantFormFields('PUT'); ?>
<input type="text" name="title" value="<?php echo $this->escape($post->title); ?>">
<textarea name="content"><?php echo $this->escape($post->content); ?></textarea>
<button type="submit">Update Post</button>
</form><form method="POST" action="<?php echo url('posts/' . $post->id); ?>"
onsubmit="return confirm('Are you sure?')">
<?php echo $this->importantFormFields('DELETE'); ?>
<button type="submit">Delete</button>
</form>// Set up CSRF token for all AJAX requests
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
// Using fetch
fetch("/api/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
csrf_token: csrfToken,
title: "My Post",
content: "Post content",
}),
})
.then((response) => response.json())
.then((data) => console.log(data));
// Using XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open("POST", "/api/posts");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(
JSON.stringify({
csrf_token: csrfToken,
title: "My Post",
})
);- Always use CSRF protection: Don't disable it unless absolutely necessary
- Use HTTPS: CSRF tokens can be intercepted over HTTP
- Keep tokens secret: Never expose tokens in URLs or logs
- Regenerate tokens: Use
persist: falsefor maximum security - Validate on server: Never rely on client-side validation alone
- Use SameSite cookies: Configure session cookies with SameSite attribute
- Enable origin checking: For additional security (if users don't switch networks)
Causes:
- Token expired (session timeout)
- Token mismatch (form cached, session regenerated)
- Origin check failed (IP or user agent changed)
- Missing token in request
Solutions:
- Increase session timeout
- Disable origin checking if users switch networks
- Ensure forms include
$this->csrf() - Check browser is sending cookies
Solution: Include CSRF token in request:
// In request body
body: JSON.stringify({
csrf_token: csrfToken,
// other data
})
// Or in headers
headers: {
'X-CSRF-Token': csrfToken
}Cause: Session not starting properly
Solution: Ensure SessionMiddleware is registered and session configuration is correct.
In Views
$this->csrf()- Generate CSRF token field$this->importantFormFields($method)- Generate CSRF + method + callback fields$this->hidden($name, $value)- Generate hidden field$this->method($verb)- Generate method spoofing field
In PHP
csrf_token()- Get current CSRF token$request->getCsrfToken()- Get or generate CSRF token$request->isSecurityValid()- Validate CSRF token and honeypot
CSRF tokens are base64-encoded and contain:
- Timestamp (for expiry)
- IP + User Agent hash (if origin checking enabled)
- Random bytes (for uniqueness)
Example: base64:dGltZXN0YW1wX2hhc2hfcmFuZG9tYnl0ZXM=