Mistake 5: Insecure Consent Storage and Transmission

Consent records prove legal basis for data processing, yet developers often treat them casually. Common mistakes include storing consent as simple boolean flags without context, transmitting consent states in URLs or insecure cookies, failing to version consent as requirements change, and not maintaining audit trails of consent changes. These practices make it impossible to demonstrate compliance during audits.

Consent is not just a yes/no flag but a complex record including what the user consented to, when they consented, how consent was obtained, and what information was provided. This rich context must be preserved securely and transmitted carefully to maintain its integrity and legal value.

// ❌ Bad: Insecure consent handling
function saveConsent(hasConsent) {
  // Too simple - no context
  localStorage.setItem('consent', hasConsent);
  
  // Insecure transmission
  fetch(`/api/consent?consent=${hasConsent}&user=${userId}`);
}

// ✅ Good: Secure consent management
class ConsentManager {
  async recordConsent(consentData) {
    const consentRecord = {
      id: this.generateConsentId(),
      userId: consentData.userId,
      timestamp: new Date().toISOString(),
      version: this.getCurrentConsentVersion(),
      purposes: consentData.purposes,
      categories: consentData.categories,
      method: consentData.method, // 'explicit', 'implicit'
      source: consentData.source, // 'banner', 'settings', 'registration'
      ipHash: await this.hashIP(consentData.ip),
      userAgent: consentData.userAgent,
      policyVersion: this.getCurrentPolicyVersion(),
      withdrawable: true,
      signature: null
    };
    
    // Create cryptographic signature
    consentRecord.signature = await this.signRecord(consentRecord);
    
    // Store securely
    await this.storeConsent(consentRecord);
    
    // Transmit securely
    await this.transmitConsent(consentRecord);
    
    return consentRecord;
  }
  
  async storeConsent(record) {
    // Store in database with encryption
    await db.consent.insert({
      ...record,
      data: await this.encrypt(JSON.stringify(record))
    });
    
    // Also store in immutable audit log
    await auditLog.append({
      type: 'consent_granted',
      record: record,
      timestamp: new Date().toISOString()
    });
  }
  
  async transmitConsent(record) {
    // Never send in URL parameters
    const response = await fetch('/api/consent', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Consent-Signature': record.signature
      },
      body: JSON.stringify(record),
      credentials: 'same-origin'
    });
    
    if (!response.ok) {
      throw new Error('Failed to record consent');
    }
  }
  
  async verifyConsent(userId, purpose) {
    const consent = await this.getLatestConsent(userId);
    
    if (!consent) return false;
    
    // Verify signature
    if (!await this.verifySignature(consent)) {
      console.error('Invalid consent signature');
      return false;
    }
    
    // Check expiry
    if (this.isConsentExpired(consent)) {
      return false;
    }
    
    // Check specific purpose
    return consent.purposes[purpose] === true;
  }
}