Skip to content

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 ​

  1. Always use HTTPS in production
  2. Set SameSite=lax or strict
  3. Rotate tokens after login
  4. Validate tokens server-side only
  5. Don't expose tokens in URLs
  6. Use short expiration times
  7. Implement rate limiting

Next Steps ​

Released under the MIT License.