CSRF Protection
Overview
Nexphant includes built-in CSRF protection for state-changing requests.
CSRF tokens prevent unauthorized commands from being transmitted from a user that the web application trusts.
Middleware
Enable CSRF middleware:
php
use Nexphant\App;
use Nexphant\Server\Middleware\{SessionMiddleware, CsrfMiddleware};
$app = App::create();
// Session required for CSRF
$app->middleware(SessionMiddleware::class);
$app->middleware(CsrfMiddleware::class);Protected Methods
CSRF protection applies to:
- POST
- PUT
- PATCH
- DELETE
GET, HEAD, OPTIONS are not protected.
Token Generation
Get Token
php
$token = csrf()->token();In Routes
php
$app->get('/form', function () {
return [
'csrf_token' => csrf()->token(),
];
});Sending Tokens
HTML Form
Include token in hidden field:
html
<form method="POST" action="/submit">
<input type="hidden" name="_token" value="<?= csrf()->token() ?>">
<input type="text" name="email">
<button type="submit">Submit</button>
</form>JSON API
Send in request body:
json
{
"_token": "abc123...",
"email": "user@example.com"
}HTTP Header
Send in X-CSRF-TOKEN header:
bash
curl -X POST http://localhost:8080/api/data \
-H "Content-Type: application/json" \
-H "X-CSRF-TOKEN: abc123..." \
-d '{"key":"value"}'Verification
Automatic
Middleware verifies automatically:
php
$app->post('/submit', function (Request $request) {
// Token already verified by middleware
$data = $request->json();
return ['success' => true];
});Manual
php
use Nexphant\Server\Csrf\CsrfManager;
$app->post('/submit', function (Request $request) {
$token = $request->json('_token');
if (!csrf()->validate($token)) {
return ['error' => 'Invalid CSRF token'];
}
return ['success' => true];
});Token Rotation
Tokens rotate automatically on:
- Session regeneration
- Login
- Logout
php
$app->post('/login', function (Request $request) {
// Authenticate user...
session()->regenerate();
csrf()->refresh(); // New token generated
return [
'success' => true,
'csrf_token' => csrf()->token(),
];
});Excluding Routes
Disable for Specific Routes
php
use Nexphant\Server\Middleware\CsrfMiddleware;
$csrf = new CsrfMiddleware();
$csrf->except([
'/webhook/*',
'/api/public/*',
]);
$app->middleware($csrf);Disable for Route Group
php
$app->group(['prefix' => '/api'], function ($group) {
// No CSRF protection in this group
$group->post('/webhook', fn() => ['ok' => true]);
});Configuration
config/csrf.php:
php
return [
'enabled' => env('CSRF_ENABLED', true),
'token_name' => '_token',
'header_name' => 'X-CSRF-TOKEN',
'except' => [
'/webhook/*',
'/api/public/*',
],
];SameSite Integration
CSRF works with SameSite cookies:
config/session.php:
php
return [
'same_site' => 'lax', // or 'strict'
];For APIs, use:
php
return [
'same_site' => 'none',
'secure' => true, // Required with 'none'
];Error Handling
Default Response
Invalid token returns:
json
{
"error": "CSRF token mismatch",
"status": 419
}Custom Handler
php
use Nexphant\Server\Middleware\CsrfMiddleware;
$csrf = new CsrfMiddleware();
$csrf->onFailure(function ($request) {
return [
'error' => 'Invalid request',
'message' => 'Please refresh and try again',
];
});
$app->middleware($csrf);Complete Example
Backend
php
use Nexphant\App;
use Nexphant\Server\Middleware\{SessionMiddleware, CsrfMiddleware};
$app = App::create();
$app->middleware(SessionMiddleware::class);
$app->middleware(CsrfMiddleware::class);
// Get token
$app->get('/form', function () {
return [
'csrf_token' => csrf()->token(),
];
});
// Submit form
$app->post('/submit', function (Request $request) {
// Token verified by middleware
$data = $request->json();
// Process data...
return ['success' => true];
});
$app->listen(port: 8080);Frontend
javascript
// Fetch CSRF token
const response = await fetch('/form');
const { csrf_token } = await response.json();
// Submit with token
await fetch('/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrf_token,
},
body: JSON.stringify({
email: 'user@example.com',
}),
});SPA Integration
Initial Token
php
$app->get('/', function () {
return view('app', [
'csrf_token' => csrf()->token(),
]);
});Meta Tag
html
<!DOCTYPE html>
<html>
<head>
<meta name="csrf-token" content="<?= csrf()->token() ?>">
</head>
<body>
<div id="app"></div>
</body>
</html>JavaScript
javascript
const token = document.querySelector('meta[name="csrf-token"]').content;
fetch('/api/data', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': token,
},
body: JSON.stringify(data),
});Testing
Disable in Tests
php
use Nexphant\Server\Csrf\CsrfManager;
// In test setup
csrf()->disable();
// Make requests without token
$response = $this->post('/submit', ['data' => 'value']);Mock Token
php
$token = csrf()->token();
$response = $this->post('/submit', [
'_token' => $token,
'data' => 'value',
]);Security Best Practices
- Always use HTTPS in production
- Set SameSite=lax or strict
- Rotate tokens after login
- Validate tokens server-side only
- Don't expose tokens in URLs
- Use short expiration times
- Implement rate limiting
