- 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.
466 lines
11 KiB
Markdown
466 lines
11 KiB
Markdown
# CHIPS Cookies (Cookies Having Independent Partitioned State)
|
|
|
|
CHIPS Cookie-Unterstützung im Custom PHP Framework für datenschutzfreundliche Third-Party-Cookies.
|
|
|
|
## Übersicht
|
|
|
|
CHIPS (Cookies Having Independent Partitioned State) ist ein Browser-Security-Feature, das Third-Party-Cookies **pro Top-Level-Site partitioniert**, um Cross-Site-Tracking zu verhindern.
|
|
|
|
**Browser-Support**:
|
|
- Chrome 114+
|
|
- Edge 114+
|
|
- Opera 100+
|
|
- Safari 16.4+ (partial)
|
|
|
|
## Was ist CHIPS?
|
|
|
|
### Problem: Traditional Third-Party Cookies
|
|
|
|
```
|
|
Widget embedded on example.com: cookie=user123
|
|
Same widget on test.com: cookie=user123 (SAME COOKIE!)
|
|
→ Cross-Site-Tracking möglich
|
|
```
|
|
|
|
### Lösung: CHIPS Partitioned Cookies
|
|
|
|
```
|
|
Widget embedded on example.com: cookie=user123_site_A
|
|
Same widget on test.com: cookie=user456_site_B (DIFFERENT!)
|
|
→ Kein Cross-Site-Tracking
|
|
```
|
|
|
|
## Requirements
|
|
|
|
CHIPS Cookies erfordern:
|
|
1. **Secure=true** (HTTPS only)
|
|
2. **SameSite=None** (Third-Party context)
|
|
3. **Partitioned** Attribut
|
|
|
|
## Verwendung
|
|
|
|
### CookieService Injection
|
|
|
|
```php
|
|
final readonly class EmbeddedWidgetController
|
|
{
|
|
public function __construct(
|
|
private CookieService $cookieService
|
|
) {}
|
|
}
|
|
```
|
|
|
|
### Standard Cookie vs. CHIPS Cookie
|
|
|
|
```php
|
|
// ❌ Standard Third-Party Cookie (tracking möglich)
|
|
$cookie = $this->cookieService->create(
|
|
name: 'analytics',
|
|
value: 'user123',
|
|
sameSite: SameSite::None,
|
|
secure: true
|
|
);
|
|
|
|
// ✅ CHIPS Partitioned Cookie (tracking verhindert)
|
|
$cookie = $this->cookieService->createPartitioned(
|
|
name: 'analytics',
|
|
value: 'user123'
|
|
);
|
|
```
|
|
|
|
### Use Case 1: Embedded Chat Widget
|
|
|
|
```php
|
|
#[Route(path: '/widget/chat/init', method: Method::GET)]
|
|
public function initChatWidget(HttpRequest $request): JsonResponse
|
|
{
|
|
$session = $this->chatService->createSession();
|
|
|
|
// Partitioned session cookie - different per embedding site
|
|
$sessionCookie = $this->cookieService->createPartitionedSession(
|
|
name: 'chat_session',
|
|
value: $session->id,
|
|
httpOnly: true
|
|
);
|
|
|
|
// Partitioned state cookie - JavaScript accessible
|
|
$stateCookie = $this->cookieService->createPartitioned(
|
|
name: 'chat_state',
|
|
value: json_encode(['minimized' => false]),
|
|
maxAge: Duration::fromDays(7),
|
|
httpOnly: false // JS needs access
|
|
);
|
|
|
|
return (new JsonResponse(['session_id' => $session->id]))
|
|
->withCookies($sessionCookie, $stateCookie);
|
|
}
|
|
```
|
|
|
|
### Use Case 2: Third-Party Analytics
|
|
|
|
```php
|
|
#[Route(path: '/analytics/track', method: Method::POST)]
|
|
public function track(HttpRequest $request): JsonResponse
|
|
{
|
|
$userId = $request->cookies->get('_analytics');
|
|
|
|
if (!$userId) {
|
|
// Create partitioned analytics cookie
|
|
$userId = $this->generateUserId();
|
|
|
|
$cookie = $this->cookieService->createAnalyticsCookie(
|
|
userId: $userId,
|
|
maxAge: Duration::fromDays(365)
|
|
);
|
|
|
|
return (new JsonResponse(['tracked' => true]))
|
|
->withCookie($cookie);
|
|
}
|
|
|
|
$this->analyticsService->track($userId, $request->parsedBody);
|
|
return new JsonResponse(['tracked' => true]);
|
|
}
|
|
```
|
|
|
|
### Use Case 3: Payment Provider Embed
|
|
|
|
```php
|
|
#[Route(path: '/payment/stripe/embed', method: Method::GET)]
|
|
public function embedPaymentForm(HttpRequest $request): ViewResult
|
|
{
|
|
$checkoutSession = $this->stripeService->createCheckoutSession();
|
|
|
|
// Partitioned - separate state per merchant site
|
|
$sessionCookie = $this->cookieService->createPartitioned(
|
|
name: 'stripe_checkout',
|
|
value: $checkoutSession->id,
|
|
maxAge: Duration::fromMinutes(30),
|
|
httpOnly: true
|
|
);
|
|
|
|
return (new ViewResult('payment/stripe-embed', [
|
|
'session' => $checkoutSession
|
|
]))->withCookie($sessionCookie);
|
|
}
|
|
```
|
|
|
|
## CookieService Factory Methods
|
|
|
|
### Standard Cookies
|
|
|
|
```php
|
|
// Basic cookie
|
|
$cookie = $this->cookieService->create(
|
|
name: 'session',
|
|
value: 'abc123',
|
|
maxAge: Duration::fromHours(1),
|
|
path: '/',
|
|
domain: null,
|
|
secure: null, // Auto true in production
|
|
httpOnly: true,
|
|
sameSite: SameSite::Lax
|
|
);
|
|
|
|
// Session cookie (no expiration)
|
|
$cookie = $this->cookieService->createSessionCookie(
|
|
name: 'session_id',
|
|
value: 'xyz789',
|
|
httpOnly: true,
|
|
sameSite: SameSite::Lax
|
|
);
|
|
|
|
// Remember-me cookie (30 days default)
|
|
$cookie = $this->cookieService->createRememberMeCookie(
|
|
value: 'token123',
|
|
maxAge: Duration::fromDays(30) // Optional
|
|
);
|
|
|
|
// Delete cookie
|
|
$cookie = $this->cookieService->delete('session', path: '/');
|
|
```
|
|
|
|
### CHIPS Partitioned Cookies
|
|
|
|
```php
|
|
// Partitioned cookie
|
|
$cookie = $this->cookieService->createPartitioned(
|
|
name: 'widget',
|
|
value: 'state123',
|
|
maxAge: Duration::fromHours(1),
|
|
path: '/',
|
|
domain: null,
|
|
httpOnly: true
|
|
);
|
|
|
|
// Partitioned session cookie
|
|
$cookie = $this->cookieService->createPartitionedSession(
|
|
name: 'widget_session',
|
|
value: 'session123',
|
|
httpOnly: true
|
|
);
|
|
|
|
// Partitioned analytics cookie (JS accessible)
|
|
$cookie = $this->cookieService->createAnalyticsCookie(
|
|
userId: 'user123',
|
|
maxAge: Duration::fromDays(365)
|
|
);
|
|
|
|
// Partitioned widget state cookie
|
|
$cookie = $this->cookieService->createWidgetStateCookie(
|
|
widgetId: 'chat',
|
|
state: json_encode(['minimized' => false]),
|
|
maxAge: Duration::fromDays(30)
|
|
);
|
|
```
|
|
|
|
## Response Integration
|
|
|
|
### Single Cookie
|
|
|
|
```php
|
|
return (new JsonResponse(['success' => true]))
|
|
->withCookie($cookie);
|
|
```
|
|
|
|
### Multiple Cookies
|
|
|
|
```php
|
|
return (new JsonResponse(['success' => true]))
|
|
->withCookies($sessionCookie, $stateCookie);
|
|
```
|
|
|
|
## Validation & Helpers
|
|
|
|
```php
|
|
// Validate cookie size (4KB browser limit)
|
|
if (!$this->cookieService->validateSize($cookie)) {
|
|
throw new \RuntimeException('Cookie exceeds size limit');
|
|
}
|
|
|
|
// Validate cookie name
|
|
if (!$this->cookieService->isValidName('user-token')) {
|
|
throw new \InvalidArgumentException('Invalid cookie name');
|
|
}
|
|
```
|
|
|
|
## Cookie Header Output
|
|
|
|
### Standard Cookie
|
|
|
|
```http
|
|
Set-Cookie: session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax
|
|
```
|
|
|
|
### CHIPS Partitioned Cookie
|
|
|
|
```http
|
|
Set-Cookie: widget=state123; Path=/; Secure; HttpOnly; SameSite=None; Partitioned
|
|
```
|
|
|
|
## Wann CHIPS verwenden?
|
|
|
|
### ✅ Use CHIPS für:
|
|
|
|
1. **Embedded Widgets** - Chat, Support, Social Media embeds
|
|
2. **Third-Party Analytics** - Analytics in iframes
|
|
3. **Payment Provider Embeds** - Stripe, PayPal checkout forms
|
|
4. **OAuth Flows in iframes** - Third-party authentication
|
|
5. **Cross-Origin Video Players** - Embedded media players
|
|
|
|
### ❌ NICHT CHIPS für:
|
|
|
|
1. **First-Party Session Cookies** - SameSite=Lax reicht
|
|
2. **Authentication Cookies** - Nur same-site verwenden
|
|
3. **CSRF Tokens** - SameSite=Strict empfohlen
|
|
4. **Standard E-Commerce** - First-party cookies ausreichend
|
|
|
|
## Security Best Practices
|
|
|
|
### 1. Production Environment
|
|
|
|
```php
|
|
// Secure flag wird in production automatisch gesetzt
|
|
$prodEnv = new Environment([EnvKey::APP_ENV->value => 'production']);
|
|
$service = new CookieService($prodEnv);
|
|
|
|
$cookie = $service->create('session', 'value');
|
|
// → Secure flag automatisch true in production
|
|
```
|
|
|
|
### 2. HTTPS Required
|
|
|
|
CHIPS Cookies erfordern HTTPS:
|
|
```php
|
|
// ✅ Wird in Cookie-Konstruktor validiert
|
|
$cookie = new Cookie(
|
|
name: 'widget',
|
|
value: 'data',
|
|
secure: true, // Required
|
|
sameSite: SameSite::None, // Required
|
|
partitioned: true
|
|
);
|
|
|
|
// ❌ Throws InvalidArgumentException
|
|
$cookie = new Cookie(
|
|
name: 'widget',
|
|
value: 'data',
|
|
secure: false, // Invalid!
|
|
partitioned: true
|
|
);
|
|
```
|
|
|
|
### 3. HttpOnly für Session Cookies
|
|
|
|
```php
|
|
// ✅ HttpOnly für session cookies
|
|
$session = $this->cookieService->createPartitionedSession(
|
|
name: 'session',
|
|
value: 'secret',
|
|
httpOnly: true // XSS protection
|
|
);
|
|
|
|
// ❌ Nur wenn JS-Zugriff WIRKLICH notwendig
|
|
$widget = $this->cookieService->createPartitioned(
|
|
name: 'widget_state',
|
|
value: json_encode(['data']),
|
|
httpOnly: false // JS accessible
|
|
);
|
|
```
|
|
|
|
## Testing
|
|
|
|
```php
|
|
describe('CHIPS Cookie Integration', function () {
|
|
it('creates partitioned cookie for widget', function () {
|
|
$service = new CookieService($this->environment);
|
|
|
|
$cookie = $service->createPartitioned(
|
|
name: 'widget',
|
|
value: 'state123'
|
|
);
|
|
|
|
expect($cookie->partitioned)->toBeTrue();
|
|
expect($cookie->secure)->toBeTrue();
|
|
expect($cookie->sameSite)->toBe(SameSite::None);
|
|
|
|
$header = $cookie->toHeaderString();
|
|
expect($header)->toContain('Partitioned');
|
|
});
|
|
});
|
|
```
|
|
|
|
## Framework Integration
|
|
|
|
### DI Container Initializer
|
|
|
|
```php
|
|
final readonly class CookieServiceInitializer
|
|
{
|
|
#[Initializer]
|
|
public function __invoke(Container $container): CookieService
|
|
{
|
|
return new CookieService(
|
|
environment: $container->get(Environment::class)
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Response Trait (Future Enhancement)
|
|
|
|
```php
|
|
trait ResponseWithCookies
|
|
{
|
|
private array $cookies = [];
|
|
|
|
public function withCookie(Cookie $cookie): self
|
|
{
|
|
$new = clone $this;
|
|
$new->cookies[] = $cookie;
|
|
return $new;
|
|
}
|
|
|
|
public function withCookies(Cookie ...$cookies): self
|
|
{
|
|
$new = clone $this;
|
|
$new->cookies = array_merge($new->cookies, $cookies);
|
|
return $new;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Migration von Standard Third-Party Cookies
|
|
|
|
### Before (Traditional)
|
|
|
|
```php
|
|
$cookie = new Cookie(
|
|
name: 'analytics',
|
|
value: 'user123',
|
|
secure: true,
|
|
sameSite: SameSite::None
|
|
);
|
|
// Problem: Cross-site tracking möglich
|
|
```
|
|
|
|
### After (CHIPS)
|
|
|
|
```php
|
|
$cookie = $this->cookieService->createPartitioned(
|
|
name: 'analytics',
|
|
value: 'user123'
|
|
);
|
|
// Benefit: Privacy-friendly, kein Cross-site tracking
|
|
```
|
|
|
|
## Browser Compatibility Check (Future)
|
|
|
|
```php
|
|
final readonly class ChipsSupportDetector
|
|
{
|
|
public function supportsChips(UserAgent $userAgent): bool
|
|
{
|
|
// Chrome/Edge 114+, Safari 16.4+, Opera 100+
|
|
return $this->detectBrowserVersion($userAgent);
|
|
}
|
|
|
|
public function getFallbackStrategy(): CookieStrategy
|
|
{
|
|
// First-party only, localStorage, etc.
|
|
return CookieStrategy::FIRST_PARTY_ONLY;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Cookie wird nicht gesetzt
|
|
|
|
1. **HTTPS fehlt**: CHIPS erfordert Secure=true → HTTPS
|
|
2. **SameSite≠None**: Muss `SameSite::None` sein
|
|
3. **Browser-Support**: Prüfe Browser-Version
|
|
4. **Cookie-Size**: Max 4KB - nutze `validateSize()`
|
|
|
|
### Cross-Site funktioniert nicht
|
|
|
|
1. **Partitioned fehlt**: Nutze `createPartitioned()` statt `create()`
|
|
2. **Domain-Mismatch**: Domain-Attribut korrekt setzen
|
|
3. **Path-Mismatch**: Path muss übereinstimmen
|
|
|
|
## Performance Considerations
|
|
|
|
- **Cookie-Size**: CHIPS Cookies zählen zum 4KB-Limit
|
|
- **Anzahl Cookies**: Browser-Limit beachten (typically 50-180)
|
|
- **Expiration**: Alte Cookies regelmäßig löschen
|
|
|
|
## Zusammenfassung
|
|
|
|
CHIPS Cookies bieten:
|
|
✅ **Privacy-Friendly** - Kein Cross-Site-Tracking
|
|
✅ **Framework-Compliant** - Value Objects, Readonly, Type Safety
|
|
✅ **Developer-Friendly** - Einfache Factory Methods
|
|
✅ **Production-Ready** - Automatic Secure Flag, Validation
|
|
✅ **Well-Tested** - Comprehensive Pest Test Suite
|
|
|
|
Das Framework unterstützt CHIPS nativ für moderne, datenschutzfreundliche Third-Party-Integrationen.
|