Files
michaelschiemer/docs/claude/chips-cookies.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

11 KiB

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

final readonly class EmbeddedWidgetController
{
    public function __construct(
        private CookieService $cookieService
    ) {}
}
// ❌ 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

#[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

#[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

#[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

// 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

// 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

return (new JsonResponse(['success' => true]))
    ->withCookie($cookie);

Multiple Cookies

return (new JsonResponse(['success' => true]))
    ->withCookies($sessionCookie, $stateCookie);

Validation & Helpers

// 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');
}
Set-Cookie: session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax
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

// 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:

// ✅ 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

// ✅ 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

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

final readonly class CookieServiceInitializer
{
    #[Initializer]
    public function __invoke(Container $container): CookieService
    {
        return new CookieService(
            environment: $container->get(Environment::class)
        );
    }
}

Response Trait (Future Enhancement)

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)

$cookie = new Cookie(
    name: 'analytics',
    value: 'user123',
    secure: true,
    sameSite: SameSite::None
);
// Problem: Cross-site tracking möglich

After (CHIPS)

$cookie = $this->cookieService->createPartitioned(
    name: 'analytics',
    value: 'user123'
);
// Benefit: Privacy-friendly, kein Cross-site tracking

Browser Compatibility Check (Future)

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

  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.