# 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
``` ### 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": "
Order created
", "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": "
Order created
", "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.