Mistake 5: Insecure Consent Storage and Transmission
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;
}
}