Files
michaelschiemer/src/Framework/LiveComponents/docs/SECURITY-GUIDE.md
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

1074 lines
29 KiB
Markdown

# LiveComponents Security Guide
Comprehensive security guide for implementing and using LiveComponents with CSRF protection, rate limiting, and idempotency.
## Table of Contents
- [Overview](#overview)
- [CSRF Protection](#csrf-protection)
- [Rate Limiting](#rate-limiting)
- [Idempotency Keys](#idempotency-keys)
- [State Encryption](#state-encryption)
- [Combined Security](#combined-security)
- [Best Practices](#best-practices)
- [Testing Security](#testing-security)
- [Troubleshooting](#troubleshooting)
---
## Overview
LiveComponents implements a **defense-in-depth security model** with four core layers:
1. **CSRF Protection** - Prevents cross-site request forgery attacks
2. **Rate Limiting** - Protects against abuse and DDoS attacks
3. **Idempotency Keys** - Prevents duplicate operations from network issues
4. **State Encryption** - Protects sensitive state data with authenticated encryption
All security features are **enabled by default** and require no configuration for basic usage.
### Security Processing Order
```
Request → CSRF Validation → Rate Limit Check → Idempotency Check → Action Execution
```
If any layer fails, the request is rejected **before** reaching your component logic.
---
## CSRF Protection
### How It Works
Every LiveComponent action requires a valid CSRF token to execute. Tokens are:
- **Per-session**: One token per user session
- **Auto-rotated**: New token generated after each action
- **Short-lived**: Expires with the session
### Automatic Token Injection
The framework automatically injects CSRF tokens into your component templates:
```html
<!-- Your template -->
<form method="POST" data-lc-action="submitForm">
<input type="text" name="email" />
<button type="submit">Submit</button>
</form>
<!-- Automatically becomes -->
<form method="POST" data-lc-action="submitForm">
<input type="hidden" name="_csrf_token" value="auto_generated_token" />
<input type="text" name="email" />
<button type="submit">Submit</button>
</form>
```
### JavaScript Integration
The LiveComponent JavaScript client automatically includes CSRF tokens:
```javascript
// Automatic - no configuration needed
liveComponent.executeAction('increment');
// The client automatically adds:
// { _csrf_token: 'session_token' }
```
### Manual Token Access
For custom forms or AJAX requests:
```php
// In your controller or component
$csrfToken = $this->session->getCsrfToken();
// In JavaScript
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
fetch('/api/livecomponents/action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
component_id: 'counter:main',
action: 'increment'
})
});
```
### CSRF Exceptions
```php
use App\Framework\Exception\Security\CsrfTokenMismatchException;
try {
$this->handler->handleAction($component, 'increment', $params);
} catch (CsrfTokenMismatchException $e) {
// Token is invalid, expired, or missing
// User should refresh the page
// Log security event
$this->owaspLogger->logSecurityEvent(
OWASPEventIdentifier::CSRF_ATTACK_DETECTED,
$request
);
return new JsonResponse([
'error' => 'Invalid security token. Please refresh the page.',
'code' => 'CSRF_TOKEN_INVALID'
], Status::FORBIDDEN);
}
```
### Testing CSRF Protection
```php
use function Pest\LiveComponents\mountComponent;
use function Pest\LiveComponents\callAction;
it('requires valid CSRF token for actions', function () {
$component = mountComponent('counter:test', ['count' => 0]);
// Without CSRF token should fail
expect(fn() => callAction($component, 'increment', [
'_csrf_token' => 'invalid_token'
]))->toThrow(CsrfTokenMismatchException::class);
});
it('accepts valid CSRF token', function () {
$component = mountComponent('counter:test', ['count' => 0]);
$session = container()->get(Session::class);
$csrfToken = $session->getCsrfToken();
$result = callAction($component, 'increment', [
'_csrf_token' => $csrfToken
]);
expect($result['state']['count'])->toBe(1);
});
```
---
## Rate Limiting
### How It Works
Rate limiting prevents abuse by restricting the number of actions per time window:
- **Per-Client**: Tracked by IP address + User ID (if authenticated)
- **Per-Action**: Each action can have custom limits
- **Configurable**: Set via `#[Action]` attribute
- **Sliding Window**: Uses token bucket algorithm
### Default Limits
```php
// Framework defaults (can be overridden)
const DEFAULT_RATE_LIMIT = 60; // requests
const DEFAULT_WINDOW = 60; // seconds
```
### Custom Rate Limits per Action
```php
use App\Framework\LiveComponents\Attributes\Action;
use App\Framework\Core\ValueObjects\Duration;
#[LiveComponent('payment')]
final readonly class PaymentComponent implements LiveComponentContract
{
// Strict limit for payment processing
#[Action(rateLimit: 5, rateLimitWindow: 300)] // 5 per 5 minutes
public function processPayment(PaymentRequest $request): ComponentData
{
// Process payment
}
// More lenient for viewing
#[Action(rateLimit: 100, rateLimitWindow: 60)] // 100 per minute
public function viewTransactions(): ComponentData
{
// View transactions
}
// No custom limit - uses framework default (60 per minute)
#[Action]
public function updateProfile(ProfileData $data): ComponentData
{
// Update profile
}
}
```
### Rate Limit Responses
When rate limit is exceeded:
```json
{
"error": "Rate limit exceeded",
"code": "RATE_LIMIT_EXCEEDED",
"retry_after": 45,
"limit": 60,
"window": 60
}
```
HTTP Status: **429 Too Many Requests**
Headers:
```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1234567890
Retry-After: 45
```
### Handling Rate Limits in JavaScript
```javascript
liveComponent.on('error', (error) => {
if (error.code === 'RATE_LIMIT_EXCEEDED') {
const retryAfter = error.retry_after;
// Show user-friendly message
showNotification(
`Too many requests. Please wait ${retryAfter} seconds.`,
'warning'
);
// Optionally schedule retry
setTimeout(() => {
liveComponent.executeAction('retry');
}, retryAfter * 1000);
}
});
```
### Rate Limit Exceptions
```php
use App\Framework\Exception\Http\RateLimitExceededException;
try {
$this->handler->handleAction($component, 'processPayment', $params);
} catch (RateLimitExceededException $e) {
$retryAfter = $e->getRetryAfter(); // seconds
// Log for monitoring
$this->logger->warning('Rate limit exceeded', [
'component' => $component->id->toString(),
'action' => 'processPayment',
'retry_after' => $retryAfter,
'client_ip' => $request->server->getClientIp()
]);
return new JsonResponse([
'error' => 'Too many payment attempts',
'retry_after' => $retryAfter
], Status::TOO_MANY_REQUESTS);
}
```
### Testing Rate Limiting
```php
it('enforces rate limits on actions', function () {
$component = mountComponent('counter:test', ['count' => 0]);
// Execute action multiple times rapidly
for ($i = 0; $i < 10; $i++) {
callAction($component, 'increment');
}
// 11th request should be rate limited
expect(fn() => callAction($component, 'increment'))
->toThrow(RateLimitExceededException::class);
});
it('includes retry-after in rate limit response', function () {
$component = mountComponent('counter:test', ['count' => 0]);
// Exhaust rate limit
for ($i = 0; $i < 10; $i++) {
callAction($component, 'increment');
}
try {
callAction($component, 'increment');
} catch (RateLimitExceededException $e) {
expect($e->getRetryAfter())->toBeGreaterThan(0);
}
});
```
---
## Idempotency Keys
### How It Works
Idempotency keys prevent duplicate operations from:
- Network timeouts
- Double-clicks
- Browser back/forward
- Client-side retries
When the same idempotency key is used twice:
- First request: **Executes action**, caches result
- Subsequent requests: **Returns cached result**, no execution
### Automatic Idempotency
The LiveComponent JavaScript client automatically generates idempotency keys for critical actions:
```javascript
// Framework automatically adds idempotency key for state-changing actions
liveComponent.executeAction('processPayment', {
amount: 100
});
// Internally becomes:
{
action: 'processPayment',
params: { amount: 100 },
idempotency_key: 'auto_generated_uuid_v4'
}
```
### Manual Idempotency Keys
For custom control or server-side operations:
```php
use App\Framework\LiveComponents\Attributes\Action;
#[LiveComponent('order')]
final readonly class OrderComponent implements LiveComponentContract
{
#[Action(idempotencyTTL: 3600)] // Cache for 1 hour
public function createOrder(
OrderRequest $request,
ActionParameters $params
): ComponentData {
// Idempotency key is automatically extracted and validated
// If duplicate key: returns cached result without executing
$order = $this->orderService->create($request);
return $this->state->withOrder($order);
}
}
```
### Custom Idempotency Key Generation
```javascript
// Generate consistent key for retry scenarios
const idempotencyKey = `order-${orderId}-${Date.now()}`;
liveComponent.executeAction('createOrder', {
order_id: orderId,
items: cartItems
}, {
idempotency_key: idempotencyKey
});
// Retry with same key - will return cached result
setTimeout(() => {
liveComponent.executeAction('createOrder', {
order_id: orderId,
items: cartItems
}, {
idempotency_key: idempotencyKey // Same key = cached result
});
}, 5000);
```
### Idempotency Metadata
Response includes metadata about idempotency:
```json
{
"html": "<div>Order created</div>",
"state": { "order_id": "12345" },
"idempotency": {
"key": "order-12345-1234567890",
"cached": false,
"ttl": 3600,
"created_at": "2024-01-15T10:30:00Z"
}
}
```
On duplicate request:
```json
{
"html": "<div>Order created</div>",
"state": { "order_id": "12345" },
"idempotency": {
"key": "order-12345-1234567890",
"cached": true, // ← Indicates cached result
"ttl": 3540,
"created_at": "2024-01-15T10:30:00Z"
}
}
```
### Idempotency TTL
Configure cache duration per action:
```php
// Short TTL for volatile operations
#[Action(idempotencyTTL: 300)] // 5 minutes
public function updateCart(): ComponentData
// Long TTL for critical operations
#[Action(idempotencyTTL: 86400)] // 24 hours
public function processPayment(): ComponentData
// No idempotency (default: 3600 seconds / 1 hour)
#[Action]
public function viewProfile(): ComponentData
```
### Testing Idempotency
```php
it('prevents duplicate action execution with same idempotency key', function () {
$component = mountComponent('counter:test', ['count' => 0]);
$idempotencyKey = 'test-key-' . uniqid();
// First execution
$result1 = callAction($component, 'increment', [
'idempotency_key' => $idempotencyKey
]);
expect($result1['state']['count'])->toBe(1);
// Second execution with same key should return cached result
$result2 = callAction($result1, 'increment', [
'idempotency_key' => $idempotencyKey
]);
// Count should still be 1 (not 2)
expect($result2['state']['count'])->toBe(1);
expect($result2['idempotency']['cached'])->toBeTrue();
});
it('allows different actions with different idempotency keys', function () {
$component = mountComponent('counter:test', ['count' => 0]);
$result1 = callAction($component, 'increment', [
'idempotency_key' => 'key-1'
]);
$result2 = callAction($result1, 'increment', [
'idempotency_key' => 'key-2'
]);
expect($result2['state']['count'])->toBe(2);
});
```
---
## State Encryption
### Overview
Sensitive LiveComponent state data is automatically encrypted using authenticated encryption (XSalsa20-Poly1305 via libsodium) to protect against:
- State tampering
- Unauthorized access to sensitive data
- Replay attacks with modified state
- Side-channel attacks
### When to Use State Encryption
Encrypt state when it contains:
- **Sensitive User Data**: Email addresses, phone numbers, personal information
- **Session Tokens**: Authentication tokens, API keys, session identifiers
- **Financial Data**: Payment information, account balances, transaction details
- **Private Business Data**: Internal IDs, unpublished content, proprietary information
### Automatic Encryption via Transformer
```php
use App\Framework\StateManagement\CacheBasedStateManager;
use App\Framework\StateManagement\EncryptionTransformer;
use App\Framework\LiveComponents\Serialization\EncryptedStateSerializer;
use App\Framework\LiveComponents\Serialization\StateEncryptor;
// 1. Setup encryption components
$encryptionKey = $vault->get('state_encryption_key'); // 32-byte key from Vault
$encryptor = new StateEncryptor($encryptionKey, $crypto, $random);
$serializer = new EncryptedStateSerializer($encryptor);
// 2. Add EncryptionTransformer to StateManager pipeline
$encryptionTransformer = new EncryptionTransformer($serializer);
$stateManager = new CacheBasedStateManager(
$cache,
transformers: [$encryptionTransformer] // Automatic encryption/decryption
);
// 3. Use normally - encryption happens transparently
$stateManager->store($componentId, $userState);
$state = $stateManager->retrieve($componentId, UserState::class);
```
### Creating Encryptable State
```php
use App\Framework\StateManagement\SerializableState;
final readonly class UserProfileState implements SerializableState
{
public function __construct(
public string $userId,
public Email $email,
public string $sessionToken, // Sensitive - will be encrypted
public array $preferences // Sensitive - will be encrypted
) {}
public function toArray(): array
{
return [
'user_id' => $this->userId,
'email' => $this->email->value,
'session_token' => $this->sessionToken,
'preferences' => $this->preferences,
];
}
public static function fromArray(array $data): self
{
return new self(
userId: $data['user_id'],
email: new Email($data['email']),
sessionToken: $data['session_token'],
preferences: $data['preferences']
);
}
}
```
### Security Features
**Authenticated Encryption**:
- **Algorithm**: XSalsa20-Poly1305 (via `sodium_crypto_secretbox`)
- **Key Size**: 256-bit (32 bytes)
- **Nonce**: 24 bytes, unique per encryption operation
- **MAC**: Poly1305 authentication tag automatically included
- **Tampering Detection**: MAC verification on every decryption
**Encrypted Data Format**:
```
[Version:1][Nonce:24 bytes][Ciphertext + MAC]
└─ Base64 encoded for cache storage
```
### Error Handling
```php
use App\Framework\LiveComponents\Exceptions\StateEncryptionException;
try {
$state = $stateManager->retrieve($componentId, UserState::class);
} catch (StateEncryptionException $e) {
// Encryption/decryption failed
// Possible causes:
// - Wrong encryption key
// - Corrupted encrypted data
// - MAC verification failed (tampering detected)
// SECURITY: Never expose encryption details to client
error_log("State decryption failed: " . $e->getMessage());
// Regenerate state from source or show error
throw new \RuntimeException('Session invalid');
}
```
### Key Management
**Generating Encryption Keys**:
```php
use App\Framework\Random\SecureRandomGenerator;
$random = new SecureRandomGenerator();
$encryptionKey = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES); // 32 bytes
// Store in Vault (encrypted at rest)
$vault->store('state_encryption_key', $encryptionKey);
```
**Key Storage Best Practices**:
- ✅ Store in Vault (encrypted at rest)
- ✅ Separate keys per environment (dev/staging/prod)
- ✅ Rotate keys quarterly
- ❌ DO NOT commit keys to version control
- ❌ DO NOT store in .env files
- ❌ DO NOT store in plain text database
### Performance
- **Encryption Overhead**: ~1-2ms per operation
- **Recommended**: Use for sensitive data only
- **Caching**: Encrypted states cached normally (encryption is transparent)
- **TTL**: Consider shorter TTL for encrypted states (balance security/performance)
### Testing Encrypted State
```php
it('encrypts and decrypts user state correctly', function () {
$userState = new UserProfileState(
userId: 'user123',
email: new Email('test@example.com'),
sessionToken: 'secret-token-xyz',
preferences: ['theme' => 'dark']
);
// Store with encryption
$stateManager->store($componentId, $userState);
// Retrieve and decrypt
$retrieved = $stateManager->retrieve($componentId, UserProfileState::class);
expect($retrieved->sessionToken)->toBe('secret-token-xyz');
expect($retrieved->preferences)->toBe(['theme' => 'dark']);
});
it('detects state tampering via MAC verification', function () {
$stateManager->store($componentId, $userState);
// Tamper with encrypted state in cache
$encrypted = $cache->get($componentId);
$tampered = substr($encrypted, 0, -5) . 'XXXXX';
$cache->set($componentId, $tampered);
// Should throw StateEncryptionException
$stateManager->retrieve($componentId, UserProfileState::class);
})->throws(StateEncryptionException::class, 'MAC verification failed');
```
### Migration to Encrypted State
```php
// Step 1: Add EncryptionTransformer to existing StateManager
$stateManager = new CacheBasedStateManager(
$cache,
transformers: [
$encryptionTransformer // Add encryption transformer
]
);
// Step 2: Existing unencrypted states will be:
// - Read normally (transformer detects unencrypted format)
// - Re-encrypted on next update
// Step 3: Monitor for encryption errors during transition
// Old states may fail decryption - regenerate from source
```
**See Also**:
- [Security Patterns - State Encryption](/docs/claude/security-patterns.md#state-encryption-for-livecomponents)
- [StateEncryptor API Reference](../Serialization/StateEncryptor.php)
- [EncryptionTransformer API Reference](../../StateManagement/EncryptionTransformer.php)
---
## Combined Security
All four security layers work together:
### Validation Order
1. **CSRF Token** - Validates authenticity
2. **Rate Limit** - Checks request frequency
3. **Idempotency Key** - Prevents duplicates
4. **Action Execution** - Runs your logic
5. **State Encryption** - Encrypts sensitive state before storage
```php
// Example: Payment processing with all security layers
#[LiveComponent('payment')]
final readonly class PaymentComponent implements LiveComponentContract
{
// Sensitive state with encryption
public function __construct(
private StateManager $stateManager // Configured with EncryptionTransformer
) {}
#[Action(
rateLimit: 5, // Max 5 payment attempts
rateLimitWindow: 300, // Per 5 minutes
idempotencyTTL: 86400 // Cache result for 24 hours
)]
public function processPayment(
PaymentRequest $request,
ActionParameters $params
): ComponentData {
// Security Layers Applied (in order):
// 1. CSRF: Already validated by framework
// 2. Rate Limit: Already checked by framework
// 3. Idempotency: Cached result returned if duplicate key
// 4. Action Execution: Only if all security checks pass
$payment = $this->paymentGateway->charge($request);
// 5. State Encryption: Sensitive payment data automatically encrypted
return $this->state->withPayment($payment);
// ↑ StateManager encrypts this state before cache storage
}
}
```
### Security Event Flow
```
1. Client sends request with CSRF token + idempotency key
2. Framework validates CSRF token
├─ Invalid → 403 Forbidden (CsrfTokenMismatchException)
└─ Valid → Continue
3. Framework checks rate limit
├─ Exceeded → 429 Too Many Requests (RateLimitExceededException)
└─ OK → Continue
4. Framework checks idempotency key
├─ Duplicate → Return cached result (no execution)
└─ New → Continue
5. Execute action and cache result
6. Return response + new CSRF token
```
### Testing Combined Security
```php
it('enforces all security layers together', function () {
$component = mountComponent('payment:test', ['balance' => 1000]);
$session = container()->get(Session::class);
$csrfToken = $session->getCsrfToken();
$idempotencyKey = 'payment-' . uniqid();
// Valid request with all security features
$result = callAction($component, 'processPayment', [
'_csrf_token' => $csrfToken,
'idempotency_key' => $idempotencyKey,
'amount' => 100
]);
expect($result['state']['balance'])->toBe(900);
// Retry with same idempotency key but new CSRF token
$newCsrfToken = $session->getCsrfToken();
$result2 = callAction($result, 'processPayment', [
'_csrf_token' => $newCsrfToken,
'idempotency_key' => $idempotencyKey,
'amount' => 100
]);
// Should return cached result due to idempotency
expect($result2['state']['balance'])->toBe(900); // Still 900, not 800
expect($result2['idempotency']['cached'])->toBeTrue();
});
```
---
## Best Practices
### 1. Always Use Idempotency for Critical Operations
```php
// ✅ Good - Payments are idempotent
#[Action(idempotencyTTL: 86400)]
public function processPayment(PaymentRequest $request): ComponentData
// ✅ Good - Order creation is idempotent
#[Action(idempotencyTTL: 3600)]
public function createOrder(OrderRequest $request): ComponentData
// ⚠️ Warning - View operations don't need idempotency
#[Action] // No idempotency needed for reads
public function viewProfile(): ComponentData
```
### 2. Set Appropriate Rate Limits
```php
// ✅ Good - Strict limits for critical actions
#[Action(rateLimit: 5, rateLimitWindow: 300)] // 5 per 5 min
public function processPayment(): ComponentData
// ✅ Good - Lenient for viewing
#[Action(rateLimit: 100, rateLimitWindow: 60)] // 100 per min
public function viewDashboard(): ComponentData
// ❌ Bad - Too lenient for critical action
#[Action(rateLimit: 1000, rateLimitWindow: 60)]
public function deleteAccount(): ComponentData
```
### 3. Handle Security Exceptions Gracefully
```php
try {
$result = $this->handler->handleAction($component, $action, $params);
} catch (CsrfTokenMismatchException $e) {
// User-friendly message
return new JsonResponse([
'error' => 'Your session has expired. Please refresh the page.',
'code' => 'SESSION_EXPIRED',
'action' => 'REFRESH_PAGE'
], Status::FORBIDDEN);
} catch (RateLimitExceededException $e) {
// Include retry information
return new JsonResponse([
'error' => 'Too many requests. Please try again later.',
'code' => 'RATE_LIMIT_EXCEEDED',
'retry_after' => $e->getRetryAfter()
], Status::TOO_MANY_REQUESTS);
}
```
### 4. Log Security Events
```php
use App\Framework\Security\OWASPSecurityLogger;
use App\Framework\Security\OWASPEventIdentifier;
// Log failed CSRF attempts
$this->owaspLogger->logSecurityEvent(
OWASPEventIdentifier::CSRF_ATTACK_DETECTED,
$request,
context: [
'component' => $componentId,
'action' => $actionName,
'expected_token' => substr($expectedToken, 0, 8) . '...',
'received_token' => substr($receivedToken, 0, 8) . '...'
]
);
// Log rate limit violations
$this->owaspLogger->logSecurityEvent(
OWASPEventIdentifier::RATE_LIMIT_EXCEEDED,
$request,
context: [
'component' => $componentId,
'action' => $actionName,
'limit' => $limit,
'window' => $window,
'retry_after' => $retryAfter
]
);
```
### 5. Test All Security Layers
```php
// Test suite should cover:
it('validates CSRF tokens', function () { /* ... */ });
it('enforces rate limits', function () { /* ... */ });
it('prevents duplicate operations', function () { /* ... */ });
it('combines all security layers', function () { /* ... */ });
it('handles security exceptions gracefully', function () { /* ... */ });
```
---
## Testing Security
Complete test examples in `tests/Feature/LiveComponents/SecurityTest.php`:
```php
describe('LiveComponent Security', function () {
describe('CSRF Protection', function () {
// ... CSRF tests
});
describe('Rate Limiting', function () {
// ... Rate limit tests
});
describe('Idempotency Keys', function () {
// ... Idempotency tests
});
describe('Combined Security Features', function () {
// ... Integration tests
});
});
```
Run security tests:
```bash
./vendor/bin/pest tests/Feature/LiveComponents/SecurityTest.php
```
---
## Troubleshooting
### CSRF Token Issues
**Problem**: "CSRF token mismatch" errors
**Causes**:
- Session expired
- Multiple browser tabs
- Cookie issues (SameSite, Secure)
- Cache/CDN stripping tokens
**Solutions**:
```php
// 1. Check session configuration
$sessionConfig = [
'cookie_lifetime' => 3600,
'cookie_secure' => true, // HTTPS only
'cookie_samesite' => 'Strict', // Prevent CSRF
'cookie_httponly' => true // No JavaScript access
];
// 2. Add token refresh mechanism
public function refreshCsrfToken(): JsonResponse
{
$newToken = $this->session->regenerateCsrfToken();
return new JsonResponse([
'csrf_token' => $newToken
]);
}
// 3. Client-side token refresh
liveComponent.on('csrf_error', async () => {
const response = await fetch('/api/csrf/refresh');
const { csrf_token } = await response.json();
// Retry original request with new token
liveComponent.setCsrfToken(csrf_token);
});
```
### Rate Limit Issues
**Problem**: Legitimate users hitting rate limits
**Causes**:
- Shared IP addresses (NAT, VPN)
- Aggressive polling/refreshing
- Automated testing without cleanup
- Incorrect rate limit configuration
**Solutions**:
```php
// 1. Use user-based limits for authenticated users
$clientId = $this->auth->check()
? ClientIdentifier::forUser($this->auth->id())
: ClientIdentifier::forIp($request->server->getClientIp());
// 2. Different limits for authenticated vs anonymous
#[Action(
rateLimit: 100, // Authenticated users
rateLimitWindow: 60,
anonymousLimit: 20 // Anonymous users
)]
// 3. Whitelist trusted IPs
$trustedIps = ['10.0.0.0/8', '172.16.0.0/12'];
if ($this->ipWhitelist->isTrusted($clientIp)) {
// Skip rate limiting
}
// 4. Clear rate limits in tests
afterEach(function () {
$cache = container()->get(Cache::class);
$cache->clear(); // Reset rate limit counters
});
```
### Idempotency Issues
**Problem**: Cached results returned when they shouldn't be
**Causes**:
- TTL too long
- Key collision
- Cache not cleared after errors
- Stale cached errors
**Solutions**:
```php
// 1. Shorter TTL for volatile data
#[Action(idempotencyTTL: 300)] // 5 minutes instead of 1 hour
// 2. Include more context in key
$idempotencyKey = sprintf(
'order-%s-user-%s-%s',
$orderId,
$userId,
$timestamp
);
// 3. Clear cache on errors
try {
$result = $this->processOrder($request);
} catch (\Exception $e) {
// Don't cache errors
$this->idempotencyService->forget($idempotencyKey);
throw $e;
}
// 4. Add version to key for breaking changes
$idempotencyKey = "v2-order-{$orderId}"; // v1 keys ignored
```
---
## Security Checklist
Before deploying to production:
- [ ] CSRF protection enabled (default: ✅)
- [ ] Rate limits configured per action
- [ ] Idempotency TTL appropriate for each action
- [ ] Security exceptions logged (OWASP events)
- [ ] HTTPS enforced (CSRF tokens require secure cookies)
- [ ] Session configuration hardened (Secure, HttpOnly, SameSite)
- [ ] Security tests passing
- [ ] Monitoring/alerting for security events
- [ ] Error messages user-friendly (no internal details)
- [ ] Rate limit responses include retry-after
---
## Additional Resources
- **Framework Documentation**: `/docs/claude/security-patterns.md`
- **OWASP Security Events**: `/docs/claude/security-patterns.md#owasp-event-integration`
- **Testing Guide**: `tests/Feature/LiveComponents/SecurityTest.php`
- **Example Components**: `src/Application/LiveComponents/Counter/CounterComponent.php`
---
## Summary
LiveComponents provides **production-ready security** out of the box:
**CSRF Protection** - Automatic token injection and validation
**Rate Limiting** - Configurable per-action limits
**Idempotency Keys** - Prevents duplicate operations
**Defense in Depth** - Multiple security layers
**Framework Integration** - OWASP logging, monitoring
**Developer-Friendly** - Automatic, minimal configuration
All security features are **enabled by default** and require **zero configuration** for basic usage. Advanced scenarios allow fine-grained control via `#[Action]` attributes.