- 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.
16 KiB
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
<!-- 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:
POST /livecomponent/component-id/action
X-CSRF-Token: generated-token-here
Content-Type: application/json
Server Validation:
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
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
// Refresh CSRF token (e.g., after long session)
LiveComponent.refreshCsrfToken().then(() => {
console.log('CSRF token refreshed');
});
Handling CSRF Errors
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:
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
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
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):
$key = RateLimitKey::forIp($request->server->getRemoteAddr());
2. Session-Based (Authenticated sessions):
$key = RateLimitKey::forSession($session->getId());
3. User-Based (Logged-in users):
$key = RateLimitKey::forUser($currentUser->id);
Handling Rate Limit Errors
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:
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:
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
// Generate client-side idempotency key
const idempotencyKey = crypto.randomUUID();
LiveComponent.executeAction('component-id', 'processPayment', {
amount: 99.99
}, {
idempotencyKey: idempotencyKey
});
Configuration
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:
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:
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:
public function render(): string
{
return $this->view('component', [
'userInput' => $this->userInput // Automatically escaped
]);
}
Template Escaping:
<!-- Automatic escaping -->
<div>{userInput}</div>
<!-- Raw HTML (use with extreme caution) -->
<div>{!! sanitizedHtml !!}</div>
Sanitization Helper:
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):
// ✅ 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
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
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:
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
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:
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
// 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
// ❌ 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
// ❌ 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
// ❌ 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
// ❌ 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 →