- 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.
29 KiB
LiveComponents Security Guide
Comprehensive security guide for implementing and using LiveComponents with CSRF protection, rate limiting, and idempotency.
Table of Contents
- Overview
- CSRF Protection
- Rate Limiting
- Idempotency Keys
- State Encryption
- Combined Security
- Best Practices
- Testing Security
- Troubleshooting
Overview
LiveComponents implements a defense-in-depth security model with four core layers:
- CSRF Protection - Prevents cross-site request forgery attacks
- Rate Limiting - Protects against abuse and DDoS attacks
- Idempotency Keys - Prevents duplicate operations from network issues
- 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:
<!-- 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:
// Automatic - no configuration needed
liveComponent.executeAction('increment');
// The client automatically adds:
// { _csrf_token: 'session_token' }
Manual Token Access
For custom forms or AJAX requests:
// 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
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
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
// Framework defaults (can be overridden)
const DEFAULT_RATE_LIMIT = 60; // requests
const DEFAULT_WINDOW = 60; // seconds
Custom Rate Limits per Action
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:
{
"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
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
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
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:
// 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:
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
// 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:
{
"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:
{
"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:
// 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
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
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
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
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:
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
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
// 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
- StateEncryptor API Reference
- EncryptionTransformer API Reference
Combined Security
All four security layers work together:
Validation Order
- CSRF Token - Validates authenticity
- Rate Limit - Checks request frequency
- Idempotency Key - Prevents duplicates
- Action Execution - Runs your logic
- State Encryption - Encrypts sensitive state before storage
// 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
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
// ✅ 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
// ✅ 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
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
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
// 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:
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:
./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:
// 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:
// 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:
// 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.