GDPR Art. 7(3) guarantees the right to withdraw consent at any time, and requires withdrawal to be 'as easy as giving consent.' ePrivacy Art. 5(3) ties this to the ongoing lawfulness of accessing device storage — once consent is withdrawn, tracking must stop. CCPA §1798.135 separately requires a permanent, always-accessible opt-out mechanism. A banner that can only be seen once — dismissed and never reachable again — makes withdrawal functionally impossible, which means any ongoing tracking after the initial consent period continues without a valid ongoing basis.
Low because the violation only affects users who change their minds after initial consent; it does not affect the initial consent act itself, reducing its severity compared to the upstream banner and enforcement checks.
Add a persistent 'Cookie Preferences' link in the site footer that re-renders the consent banner pre-populated with the current stored state. The link must appear on every page.
// components/Footer.tsx
'use client'
import { useState } from 'react'
import { CookieBanner } from '@/components/CookieBanner'
import { saveConsent } from '@/lib/consent'
export function Footer() {
const [show, setShow] = useState(false)
return (
<footer>
<button
onClick={() => setShow(true)}
className="text-sm text-gray-500 underline hover:text-gray-700"
>
Cookie Preferences
</button>
{show && (
<CookieBanner
showCurrentPreferences
onSave={(state) => { saveConsent(state); setShow(false) }}
/>
)}
</footer>
)
}
When analytics consent is withdrawn, call window.gtag?.('consent', 'update', { analytics_storage: 'denied' }) immediately to stop GA4 measurement for the current session.
ID: cookie-consent-compliance.consent-enforcement.consent-withdrawal
Severity: low
What to look for: Check whether there is a persistent, always-accessible mechanism for users to change their consent preferences after the initial banner is dismissed. Common implementations: (1) a "Cookie Preferences" or "Manage cookies" link in the page footer (present on all pages), (2) a button in user account settings, (3) a floating cookie preferences icon. Find the footer component and look for such a link. Verify the link opens the consent banner again or a preferences panel. Check that selecting "Reject all" in the preferences panel actually removes previously set analytics cookies (or at least stops loading them) — some implementations store the preference correctly but leave analytics cookies already set from before withdrawal in place. Removing cookies on withdrawal (document.cookie = 'name=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;') is the correct behavior.
Pass criteria: Count all consent withdrawal access points. A persistent, clearly labeled "Cookie Preferences" or "Manage cookies" mechanism is present and accessible from all pages (typically in the footer). Clicking it allows users to review and change their consent categories. After changing preferences, the changes take effect immediately (scripts unloaded or paused where technically possible; preference stored for next page load). At least 1 persistent link or button must be available to modify consent at any time.
Fail criteria: No persistent consent withdrawal mechanism exists. Banner can only be seen once — users have no way to change preferences after dismissing it. Cookie preferences link exists but is dead or links to the cookie policy page without an interactive consent mechanism.
Skip (N/A) when: Application sets only essential cookies with no non-essential categories — no preferences to manage.
Detail on fail: Example: "No cookie preferences link found in footer or anywhere else in the application. Users have no way to change consent after initial banner dismissal." or "Footer has a 'Cookie Policy' link that goes to the cookie policy text page, but no interactive consent management.".
Remediation: Add a footer link that re-triggers the consent banner:
// components/Footer.tsx
'use client'
import { useState } from 'react'
import { CookieBanner } from '@/components/CookieBanner'
export function Footer() {
const [showPreferences, setShowPreferences] = useState(false)
return (
<footer>
{/* ... other footer content ... */}
<button
onClick={() => setShowPreferences(true)}
className="text-sm text-gray-500 underline"
>
Cookie Preferences
</button>
{showPreferences && (
<CookieBanner
onSave={(state) => {
saveConsent(state)
setShowPreferences(false)
}}
showCurrentPreferences // prop to pre-populate with stored state
/>
)}
</footer>
)
}
When a user withdraws consent for analytics, call the analytics tool's opt-out method if available (e.g., window.gtag?.('consent', 'update', { analytics_storage: 'denied' })) and stop future tracking immediately. You cannot retroactively delete data already sent to analytics, but you can stop further collection and note the withdrawal timestamp.