Tutorial 1: Building a Complete Cookie Consent System

This tutorial walks through creating a production-ready cookie consent system that handles multiple jurisdictions, remembers user preferences, and integrates with popular analytics and advertising platforms. The implementation includes both frontend and backend components, providing a complete solution.

// Frontend: Cookie consent banner component
class CookieConsentBanner {
  constructor() {
    this.consentEndpoint = '/api/privacy/consent';
    this.cookieName = 'privacy_consent';
    this.cookieMaxAge = 365 * 24 * 60 * 60; // 1 year
    this.jurisdiction = null;
    this.consentState = null;
    
    this.init();
  }

  async init() {
    // Detect user jurisdiction
    this.jurisdiction = await this.detectJurisdiction();
    
    // Check existing consent
    this.consentState = this.getStoredConsent();
    
    // Show banner if needed
    if (!this.consentState || this.isConsentExpired()) {
      this.renderBanner();
    } else {
      this.applyConsent();
    }
    
    // Listen for consent events
    this.setupEventListeners();
  }

  async detectJurisdiction() {
    try {
      const response = await fetch('/api/privacy/jurisdiction');
      const data = await response.json();
      return data.jurisdiction; // 'EU', 'CA', 'US', etc.
    } catch {
      return 'EU'; // Default to most restrictive
    }
  }

  renderBanner() {
    const template = this.getTemplate();
    const banner = document.createElement('div');
    banner.className = 'cookie-consent-banner';
    banner.innerHTML = template;
    
    // Add to page with animation
    document.body.appendChild(banner);
    requestAnimationFrame(() => {
      banner.classList.add('visible');
    });
    
    // Setup form handlers
    this.setupFormHandlers(banner);
    
    // Trap focus for accessibility
    this.trapFocus(banner);
  }

  getTemplate() {
    const templates = {
      EU: `
        <div class="consent-content">
          <h2>We value your privacy</h2>
          <p>We use cookies and similar technologies to provide you with a better experience, 
             analyze site traffic, and serve targeted advertisements. By continuing to use 
             this site, you consent to our use of cookies.</p>
          
          <div class="consent-options" id="detailed-options" style="display: none;">
            <div class="consent-category">
              <label>
                <input type="checkbox" name="necessary" checked disabled>
                <strong>Necessary</strong>
                <p>Required for the website to function properly</p>
              </label>
            </div>
            
            <div class="consent-category">
              <label>
                <input type="checkbox" name="analytics">
                <strong>Analytics</strong>
                <p>Help us understand how visitors interact with our website</p>
              </label>
            </div>
            
            <div class="consent-category">
              <label>
                <input type="checkbox" name="marketing">
                <strong>Marketing</strong>
                <p>Used to deliver relevant advertisements</p>
              </label>
            </div>
            
            <div class="consent-category">
              <label>
                <input type="checkbox" name="preferences">
                <strong>Preferences</strong>
                <p>Remember your settings and preferences</p>
              </label>
            </div>
          </div>
          
          <div class="consent-actions">
            <button class="btn-primary" data-action="accept-all">Accept All</button>
            <button class="btn-secondary" data-action="accept-selected">Accept Selected</button>
            <button class="btn-text" data-action="show-details">Manage Preferences</button>
            <a href="/privacy-policy" class="privacy-link">Privacy Policy</a>
          </div>
        </div>
      `,
      
      CA: `
        <div class="consent-content">
          <h2>Cookie Notice</h2>
          <p>We use cookies to improve your experience. You can manage your preferences below.</p>
          
          <div class="consent-actions">
            <button class="btn-primary" data-action="accept-all">OK</button>
            <button class="btn-text" data-action="show-details">Manage Cookies</button>
            <a href="/privacy/do-not-sell" class="ccpa-link">Do Not Sell My Personal Information</a>
          </div>
        </div>
      `
    };
    
    return templates[this.jurisdiction] || templates.EU;
  }

  setupFormHandlers(banner) {
    // Accept all handler
    banner.querySelector('[data-action="accept-all"]').addEventListener('click', () => {
      this.saveConsent({
        necessary: true,
        analytics: true,
        marketing: true,
        preferences: true
      });
      this.closeBanner();
    });
    
    // Accept selected handler
    const acceptSelected = banner.querySelector('[data-action="accept-selected"]');
    if (acceptSelected) {
      acceptSelected.addEventListener('click', () => {
        const consent = this.getSelectedConsent(banner);
        this.saveConsent(consent);
        this.closeBanner();
      });
    }
    
    // Show details handler
    banner.querySelector('[data-action="show-details"]').addEventListener('click', () => {
      const options = banner.querySelector('#detailed-options');
      options.style.display = options.style.display === 'none' ? 'block' : 'none';
      
      // Update button text
      const btn = banner.querySelector('[data-action="show-details"]');
      btn.textContent = options.style.display === 'none' 
        ? 'Manage Preferences' 
        : 'Hide Preferences';
    });
  }

  getSelectedConsent(banner) {
    const consent = { necessary: true }; // Always true
    
    const checkboxes = banner.querySelectorAll('input[type="checkbox"]:not([name="necessary"])');
    checkboxes.forEach(checkbox => {
      consent[checkbox.name] = checkbox.checked;
    });
    
    return consent;
  }

  async saveConsent(consent) {
    const consentRecord = {
      consent,
      timestamp: new Date().toISOString(),
      jurisdiction: this.jurisdiction,
      consentVersion: '1.0',
      userAgent: navigator.userAgent,
      source: 'banner'
    };
    
    // Save locally
    this.setCookie(this.cookieName, JSON.stringify(consentRecord));
    this.consentState = consentRecord;
    
    // Save to server
    try {
      await fetch(this.consentEndpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(consentRecord)
      });
    } catch (error) {
      console.error('Failed to save consent to server:', error);
    }
    
    // Apply consent immediately
    this.applyConsent();
  }

  applyConsent() {
    if (!this.consentState) return;
    
    const consent = this.consentState.consent;
    
    // Google Analytics
    if (window.gtag) {
      gtag('consent', 'update', {
        'analytics_storage': consent.analytics ? 'granted' : 'denied',
        'ads_storage': consent.marketing ? 'granted' : 'denied'
      });
    }
    
    // Facebook Pixel
    if (window.fbq) {
      if (!consent.marketing) {
        fbq('dataProcessingOptions', ['LDU'], 1, 1000);
      }
    }
    
    // Custom event for other scripts
    window.dispatchEvent(new CustomEvent('consentUpdated', {
      detail: consent
    }));
  }

  setCookie(name, value) {
    const date = new Date();
    date.setTime(date.getTime() + (this.cookieMaxAge * 1000));
    document.cookie = `${name}=${encodeURIComponent(value)};` +
                     `expires=${date.toUTCString()};` +
                     `path=/;` +
                     `SameSite=Lax;` +
                     `Secure`;
  }

  closeBanner() {
    const banner = document.querySelector('.cookie-consent-banner');
    if (banner) {
      banner.classList.remove('visible');
      setTimeout(() => banner.remove(), 300);
    }
  }
}

// Backend: Consent API endpoint
const express = require('express');
const router = express.Router();

// POST /api/privacy/consent
router.post('/consent', async (req, res) => {
  try {
    const consentData = req.body;
    
    // Validate consent data
    if (!isValidConsent(consentData)) {
      return res.status(400).json({ error: 'Invalid consent data' });
    }
    
    // Add server-side metadata
    const enrichedConsent = {
      ...consentData,
      ipHash: hashIP(req.ip),
      sessionId: req.session.id,
      serverTimestamp: new Date().toISOString()
    };
    
    // Store in database
    await storeConsent(enrichedConsent);
    
    // Update user preferences
    if (req.user) {
      await updateUserPreferences(req.user.id, consentData.consent);
    }
    
    res.json({ success: true, consentId: enrichedConsent.id });
  } catch (error) {
    console.error('Consent storage error:', error);
    res.status(500).json({ error: 'Failed to save consent' });
  }
});

// GET /api/privacy/jurisdiction
router.get('/jurisdiction', async (req, res) => {
  try {
    // Use GeoIP to determine jurisdiction
    const geoData = await geoip.lookup(req.ip);
    const jurisdiction = mapCountryToJurisdiction(geoData.country);
    
    res.json({ 
      jurisdiction,
      country: geoData.country,
      region: geoData.region
    });
  } catch (error) {
    // Default to EU (most restrictive)
    res.json({ jurisdiction: 'EU' });
  }
});

// Helper functions
function isValidConsent(data) {
  return data.consent 
    && typeof data.consent === 'object'
    && data.timestamp
    && data.jurisdiction
    && data.consentVersion;
}

function hashIP(ip) {
  return crypto
    .createHash('sha256')
    .update(ip + process.env.IP_SALT)
    .digest('hex')
    .substring(0, 16);
}

async function storeConsent(consentData) {
  const consent = await db.consent.create({
    data: {
      ...consentData,
      id: generateConsentId()
    }
  });
  
  // Also store in time-series database for analytics
  await timeseriesDB.insert('consent_events', {
    timestamp: consentData.serverTimestamp,
    jurisdiction: consentData.jurisdiction,
    categories: consentData.consent,
    source: consentData.source
  });
  
  return consent;
}