- 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.
692 lines
16 KiB
Markdown
692 lines
16 KiB
Markdown
# LiveComponents Security Guide
|
|
|
|
**Production-Ready Security for Interactive Components**
|
|
|
|
This guide covers security best practices, built-in protections, and threat mitigation strategies for LiveComponents.
|
|
|
|
## Security Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────┐
|
|
│ Security Layers (Defense in Depth) │
|
|
├─────────────────────────────────────────────────┤
|
|
│ 1. CSRF Protection (Token Validation) │
|
|
│ 2. Rate Limiting (DoS Prevention) │
|
|
│ 3. Idempotency Keys (Replay Protection) │
|
|
│ 4. Input Validation (XSS/Injection) │
|
|
│ 5. Action Authorization (Access Control) │
|
|
│ 6. State Encryption (Data Protection) │
|
|
└─────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## 1. CSRF Protection
|
|
|
|
### Automatic CSRF Protection
|
|
|
|
**All LiveComponent requests are automatically protected** against Cross-Site Request Forgery attacks.
|
|
|
|
#### How It Works
|
|
|
|
```html
|
|
<!-- 1. CSRF token in meta tag -->
|
|
<meta name="csrf-token" content="generated-token-here">
|
|
|
|
<!-- 2. Automatically included in requests -->
|
|
<button data-lc-action="deleteAccount">Delete Account</button>
|
|
|
|
<!-- 3. Server validates token -->
|
|
```
|
|
|
|
**Request Headers**:
|
|
```http
|
|
POST /livecomponent/component-id/action
|
|
X-CSRF-Token: generated-token-here
|
|
Content-Type: application/json
|
|
```
|
|
|
|
**Server Validation**:
|
|
```php
|
|
final readonly class CsrfMiddleware
|
|
{
|
|
public function process(Request $request, callable $next): Response
|
|
{
|
|
if ($this->isStateChanging($request)) {
|
|
$this->validator->validate($request);
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Configuration
|
|
|
|
```env
|
|
# .env
|
|
LIVECOMPONENT_CSRF_PROTECTION=true # Enable/disable
|
|
CSRF_TOKEN_LIFETIME=7200 # 2 hours
|
|
CSRF_REGENERATE_ON_ACTION=false # Regenerate after each action
|
|
```
|
|
|
|
### Manual CSRF Token Refresh
|
|
|
|
```javascript
|
|
// Refresh CSRF token (e.g., after long session)
|
|
LiveComponent.refreshCsrfToken().then(() => {
|
|
console.log('CSRF token refreshed');
|
|
});
|
|
```
|
|
|
|
### Handling CSRF Errors
|
|
|
|
```php
|
|
use App\Framework\Exception\Security\CsrfTokenMismatchException;
|
|
|
|
try {
|
|
$this->executeAction($component, $action);
|
|
} catch (CsrfTokenMismatchException $e) {
|
|
return new JsonResponse([
|
|
'success' => false,
|
|
'error' => 'Your session has expired. Please refresh the page.',
|
|
'code' => 'CSRF_TOKEN_EXPIRED'
|
|
], Status::FORBIDDEN);
|
|
}
|
|
```
|
|
|
|
**Client-Side Handling**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:error', (e) => {
|
|
if (e.detail.code === 'CSRF_TOKEN_EXPIRED') {
|
|
// Refresh token and retry
|
|
LiveComponent.refreshCsrfToken().then(() => {
|
|
LiveComponent.retryLastAction();
|
|
});
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Rate Limiting
|
|
|
|
### Per-Component Rate Limits
|
|
|
|
Protect against abuse and DoS attacks with intelligent rate limiting.
|
|
|
|
#### Component-Level Configuration
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\RateLimit;
|
|
|
|
#[RateLimit(requests: 10, window: 60)] // 10 requests per 60 seconds
|
|
final class SearchComponent extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[RateLimit(requests: 5, window: 60)] // Stricter limit for this action
|
|
public function performSearch(): void
|
|
{
|
|
// Expensive search operation
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Global Configuration
|
|
|
|
```env
|
|
# .env
|
|
LIVECOMPONENT_RATE_LIMIT=60 # Default: 60 requests/minute
|
|
LIVECOMPONENT_RATE_LIMIT_WINDOW=60 # Window in seconds
|
|
LIVECOMPONENT_RATE_LIMIT_BY=ip # 'ip', 'session', or 'user'
|
|
```
|
|
|
|
### Rate Limit Strategies
|
|
|
|
**1. IP-Based** (Anonymous users):
|
|
```php
|
|
$key = RateLimitKey::forIp($request->server->getRemoteAddr());
|
|
```
|
|
|
|
**2. Session-Based** (Authenticated sessions):
|
|
```php
|
|
$key = RateLimitKey::forSession($session->getId());
|
|
```
|
|
|
|
**3. User-Based** (Logged-in users):
|
|
```php
|
|
$key = RateLimitKey::forUser($currentUser->id);
|
|
```
|
|
|
|
### Handling Rate Limit Errors
|
|
|
|
```php
|
|
use App\Framework\Exception\Http\RateLimitExceededException;
|
|
|
|
try {
|
|
$this->executeAction($component, $action);
|
|
} catch (RateLimitExceededException $e) {
|
|
return new JsonResponse([
|
|
'success' => false,
|
|
'error' => 'Too many requests. Please slow down.',
|
|
'retry_after' => $e->getRetryAfter()->toSeconds()
|
|
], Status::TOO_MANY_REQUESTS);
|
|
}
|
|
```
|
|
|
|
**Client-Side**:
|
|
```javascript
|
|
window.addEventListener('livecomponent:rate-limited', (e) => {
|
|
const retryAfter = e.detail.retryAfter;
|
|
|
|
// Show countdown to user
|
|
showNotification(`Too many requests. Try again in ${retryAfter} seconds.`);
|
|
|
|
// Disable actions temporarily
|
|
disableActionsFor(retryAfter);
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Idempotency Keys
|
|
|
|
Prevent duplicate submissions and ensure operations execute exactly once.
|
|
|
|
### Automatic Idempotency
|
|
|
|
**Critical actions** are automatically protected with idempotency keys:
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\Idempotent;
|
|
|
|
final class PaymentComponent extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[Idempotent] // Ensures execute-once semantics
|
|
public function processPayment(): void
|
|
{
|
|
$this->paymentGateway->charge($this->amount);
|
|
$this->orderService->createOrder($this->cartItems);
|
|
}
|
|
}
|
|
```
|
|
|
|
### How It Works
|
|
|
|
```
|
|
Client Server
|
|
│ │
|
|
├─ POST (idempotency_key: abc) ─>│ 1. Store key → Execute action
|
|
│ │
|
|
├─ POST (idempotency_key: abc) ─>│ 2. Key exists → Return cached result
|
|
│<─ Cached Result ─────────────┤ (No re-execution)
|
|
```
|
|
|
|
### Manual Idempotency Keys
|
|
|
|
```javascript
|
|
// Generate client-side idempotency key
|
|
const idempotencyKey = crypto.randomUUID();
|
|
|
|
LiveComponent.executeAction('component-id', 'processPayment', {
|
|
amount: 99.99
|
|
}, {
|
|
idempotencyKey: idempotencyKey
|
|
});
|
|
```
|
|
|
|
### Configuration
|
|
|
|
```env
|
|
LIVECOMPONENT_IDEMPOTENCY_ENABLED=true
|
|
LIVECOMPONENT_IDEMPOTENCY_TTL=3600 # Cache results for 1 hour
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Input Validation
|
|
|
|
### Server-Side Validation (Required)
|
|
|
|
**Never trust client input**. Always validate on the server:
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\Validated;
|
|
use App\Framework\Validation\Rules;
|
|
|
|
final class RegistrationComponent extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
#[Validated([
|
|
Rules::required(),
|
|
Rules::email(),
|
|
Rules::maxLength(255)
|
|
])]
|
|
public string $email = '';
|
|
|
|
#[LiveProp]
|
|
#[Validated([
|
|
Rules::required(),
|
|
Rules::minLength(8),
|
|
Rules::containsUppercase(),
|
|
Rules::containsNumber()
|
|
])]
|
|
public string $password = '';
|
|
|
|
#[LiveAction]
|
|
public function register(): void
|
|
{
|
|
// Validation happens automatically before action execution
|
|
// If validation fails, action is not executed
|
|
|
|
$this->userService->register(
|
|
new Email($this->email),
|
|
new Password($this->password)
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Value Object Validation
|
|
|
|
**Best Practice**: Use Value Objects for automatic validation:
|
|
|
|
```php
|
|
final readonly class Email
|
|
{
|
|
public function __construct(public string $value)
|
|
{
|
|
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
|
|
throw new InvalidArgumentException('Invalid email format');
|
|
}
|
|
|
|
// Additional security checks
|
|
if ($this->containsSuspiciousPatterns($value)) {
|
|
throw new SecurityException('Email contains suspicious patterns');
|
|
}
|
|
}
|
|
|
|
private function containsSuspiciousPatterns(string $email): bool
|
|
{
|
|
// Detect SQL injection attempts
|
|
if (preg_match('/[;\'"<>]/', $email)) {
|
|
return true;
|
|
}
|
|
|
|
// Detect XSS attempts
|
|
if (preg_match('/<script|javascript:/i', $email)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
```
|
|
|
|
### XSS Protection
|
|
|
|
**Automatic HTML Escaping**:
|
|
|
|
```php
|
|
public function render(): string
|
|
{
|
|
return $this->view('component', [
|
|
'userInput' => $this->userInput // Automatically escaped
|
|
]);
|
|
}
|
|
```
|
|
|
|
**Template Escaping**:
|
|
```html
|
|
<!-- Automatic escaping -->
|
|
<div>{userInput}</div>
|
|
|
|
<!-- Raw HTML (use with extreme caution) -->
|
|
<div>{!! sanitizedHtml !!}</div>
|
|
```
|
|
|
|
**Sanitization Helper**:
|
|
```php
|
|
use App\Framework\Security\HtmlSanitizer;
|
|
|
|
final class CommentComponent extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public string $comment = '';
|
|
|
|
#[LiveAction]
|
|
public function postComment(): void
|
|
{
|
|
// Sanitize before storage
|
|
$sanitized = $this->htmlSanitizer->sanitize($this->comment);
|
|
|
|
$this->commentRepository->save(new Comment($sanitized));
|
|
}
|
|
}
|
|
```
|
|
|
|
### SQL Injection Protection
|
|
|
|
**Use Parameterized Queries** (framework automatic):
|
|
|
|
```php
|
|
// ✅ Safe - parameterized
|
|
$users = $this->db->query(
|
|
'SELECT * FROM users WHERE email = ?',
|
|
[$this->email]
|
|
);
|
|
|
|
// ❌ NEVER do this - vulnerable to SQL injection
|
|
$users = $this->db->query(
|
|
"SELECT * FROM users WHERE email = '{$this->email}'"
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Action Authorization
|
|
|
|
### Role-Based Access Control
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\Authorize;
|
|
|
|
final class AdminDashboard extends LiveComponent
|
|
{
|
|
#[LiveAction]
|
|
#[Authorize(roles: ['admin'])]
|
|
public function deleteUser(string $userId): void
|
|
{
|
|
$this->userService->delete($userId);
|
|
}
|
|
|
|
#[LiveAction]
|
|
#[Authorize(permissions: ['users.edit'])]
|
|
public function updateUserRole(string $userId, string $role): void
|
|
{
|
|
$this->userService->updateRole($userId, $role);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Custom Authorization
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\LiveAction;
|
|
|
|
final class PostEditor extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
public string $postId = '';
|
|
|
|
#[LiveAction]
|
|
public function publish(): void
|
|
{
|
|
// Custom authorization logic
|
|
$post = $this->postRepository->find($this->postId);
|
|
|
|
if (!$this->canPublish($post)) {
|
|
throw new UnauthorizedException('You cannot publish this post');
|
|
}
|
|
|
|
$post->publish();
|
|
$this->postRepository->save($post);
|
|
}
|
|
|
|
private function canPublish(Post $post): bool
|
|
{
|
|
// Owner can always publish
|
|
if ($post->authorId === $this->currentUser->id) {
|
|
return true;
|
|
}
|
|
|
|
// Editors can publish if approved
|
|
if ($this->currentUser->hasRole('editor') && $post->isApproved()) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. State Encryption
|
|
|
|
### Sensitive Data Protection
|
|
|
|
**Encrypt sensitive component state** before sending to client:
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\Encrypted;
|
|
|
|
final class PaymentComponent extends LiveComponent
|
|
{
|
|
#[LiveProp]
|
|
#[Encrypted] // Encrypted in client state
|
|
public string $creditCardNumber = '';
|
|
|
|
#[LiveProp]
|
|
#[Encrypted]
|
|
public string $cvv = '';
|
|
|
|
#[LiveProp] // Not encrypted - safe to expose
|
|
public string $cardholderName = '';
|
|
}
|
|
```
|
|
|
|
### How It Works
|
|
|
|
```
|
|
Server → Encrypt → Client → Store encrypted → Send back → Server → Decrypt
|
|
```
|
|
|
|
**Never expose sensitive data in plain text to the client**.
|
|
|
|
### Configuration
|
|
|
|
```env
|
|
LIVECOMPONENT_STATE_ENCRYPTION=true
|
|
VAULT_ENCRYPTION_KEY=base64:your-256-bit-key-here
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Security Headers
|
|
|
|
### Automatic Security Headers
|
|
|
|
Framework automatically sets security headers for LiveComponent responses:
|
|
|
|
```http
|
|
X-Frame-Options: SAMEORIGIN
|
|
X-Content-Type-Options: nosniff
|
|
X-XSS-Protection: 1; mode=block
|
|
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
|
|
Referrer-Policy: strict-origin-when-cross-origin
|
|
```
|
|
|
|
### Configure CSP for LiveComponents
|
|
|
|
```php
|
|
// config/security.php
|
|
return [
|
|
'csp' => [
|
|
'default-src' => ["'self'"],
|
|
'script-src' => ["'self'", "'unsafe-inline'"], // Required for inline event handlers
|
|
'style-src' => ["'self'", "'unsafe-inline'"], // Required for scoped styles
|
|
'connect-src' => ["'self'", 'wss://yourdomain.com'], // For SSE
|
|
]
|
|
];
|
|
```
|
|
|
|
---
|
|
|
|
## 8. OWASP Top 10 Coverage
|
|
|
|
### A01: Broken Access Control
|
|
- ✅ `#[Authorize]` attribute for actions
|
|
- ✅ Custom authorization logic
|
|
- ✅ Session-based authentication
|
|
|
|
### A02: Cryptographic Failures
|
|
- ✅ Encrypted state for sensitive data
|
|
- ✅ HTTPS enforced in production
|
|
- ✅ Secure random token generation
|
|
|
|
### A03: Injection
|
|
- ✅ Parameterized queries (automatic)
|
|
- ✅ HTML escaping (automatic)
|
|
- ✅ Value Object validation
|
|
|
|
### A04: Insecure Design
|
|
- ✅ Idempotency for critical actions
|
|
- ✅ Rate limiting per component
|
|
- ✅ Defense in depth architecture
|
|
|
|
### A05: Security Misconfiguration
|
|
- ✅ Secure defaults
|
|
- ✅ Environment-based configuration
|
|
- ✅ Security headers automatic
|
|
|
|
### A06: Vulnerable Components
|
|
- ✅ Dependency scanning (Composer)
|
|
- ✅ Regular framework updates
|
|
- ✅ Minimal external dependencies
|
|
|
|
### A07: Authentication Failures
|
|
- ✅ Session-based auth
|
|
- ✅ CSRF protection
|
|
- ✅ Rate limiting on auth actions
|
|
|
|
### A08: Software Integrity Failures
|
|
- ✅ Idempotency keys
|
|
- ✅ State encryption
|
|
- ✅ Request signing
|
|
|
|
### A09: Logging Failures
|
|
- ✅ Security event logging (OWASP)
|
|
- ✅ Action audit trail
|
|
- ✅ Error tracking
|
|
|
|
### A10: Server-Side Request Forgery
|
|
- ✅ URL validation
|
|
- ✅ Whitelist-based external requests
|
|
- ✅ Network isolation
|
|
|
|
---
|
|
|
|
## Production Security Checklist
|
|
|
|
### Pre-Deployment
|
|
|
|
- [ ] Enable CSRF protection (`LIVECOMPONENT_CSRF_PROTECTION=true`)
|
|
- [ ] Configure rate limiting for all components
|
|
- [ ] Add `#[Idempotent]` to critical actions (payments, orders)
|
|
- [ ] Validate all user inputs with `#[Validated]` or Value Objects
|
|
- [ ] Add `#[Authorize]` to admin/privileged actions
|
|
- [ ] Encrypt sensitive state with `#[Encrypted]`
|
|
- [ ] Review and test all authorization logic
|
|
- [ ] Set up OWASP security event logging
|
|
- [ ] Configure security headers and CSP
|
|
- [ ] Enable HTTPS and HSTS headers
|
|
|
|
### Monitoring
|
|
|
|
- [ ] Monitor rate limit violations
|
|
- [ ] Track CSRF token errors
|
|
- [ ] Alert on repeated authorization failures
|
|
- [ ] Log all idempotency key violations
|
|
- [ ] Track input validation failures
|
|
- [ ] Monitor for unusual activity patterns
|
|
|
|
### Incident Response
|
|
|
|
- [ ] Have rollback plan for compromised components
|
|
- [ ] Document security event response procedures
|
|
- [ ] Set up automated alerts for critical security events
|
|
- [ ] Regular security audit schedule
|
|
- [ ] Penetration testing for critical components
|
|
|
|
---
|
|
|
|
## Security Best Practices
|
|
|
|
### 1. Principle of Least Privilege
|
|
|
|
```php
|
|
// ❌ Bad - expose too much
|
|
#[LiveProp]
|
|
public User $currentUser;
|
|
|
|
// ✅ Good - expose only what's needed
|
|
#[LiveProp]
|
|
public string $currentUserName;
|
|
|
|
#[LiveProp]
|
|
public bool $isAdmin;
|
|
```
|
|
|
|
### 2. Validate, Don't Filter
|
|
|
|
```php
|
|
// ❌ Bad - filtering can be bypassed
|
|
$email = filter_var($input, FILTER_SANITIZE_EMAIL);
|
|
|
|
// ✅ Good - validate and reject invalid input
|
|
if (!filter_var($input, FILTER_VALIDATE_EMAIL)) {
|
|
throw new InvalidArgumentException('Invalid email');
|
|
}
|
|
$email = $input;
|
|
```
|
|
|
|
### 3. Fail Securely
|
|
|
|
```php
|
|
// ❌ Bad - defaults to allowing access
|
|
if ($this->currentUser->hasRole('admin') ?? true) {
|
|
$this->deleteUser();
|
|
}
|
|
|
|
// ✅ Good - defaults to denying access
|
|
if ($this->currentUser?->hasRole('admin') === true) {
|
|
$this->deleteUser();
|
|
} else {
|
|
throw new UnauthorizedException();
|
|
}
|
|
```
|
|
|
|
### 4. Never Trust Client State
|
|
|
|
```php
|
|
// ❌ Bad - trust client-provided data
|
|
#[LiveProp]
|
|
public bool $isAdmin = false; // Client can modify this!
|
|
|
|
// ✅ Good - compute from server-side source of truth
|
|
public function isAdmin(): bool
|
|
{
|
|
return $this->currentUser?->hasRole('admin') ?? false;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Reporting Security Issues
|
|
|
|
**Please do not open public issues for security vulnerabilities.**
|
|
|
|
Email: security@example.com
|
|
|
|
Include:
|
|
- Component name and version
|
|
- Detailed description of vulnerability
|
|
- Steps to reproduce
|
|
- Potential impact assessment
|
|
|
|
We aim to respond within 24 hours and provide fixes within 7 days for critical vulnerabilities.
|
|
|
|
---
|
|
|
|
**Next**: [Performance Guide](performance-guide.md) →
|