Files
michaelschiemer/docs/livecomponents/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

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