COPPA §312.2 and §312.8 define 'personal information' to include persistent identifiers that track a child over time or across websites — explicitly including cookies and device IDs. Google Analytics' _ga cookie (2-year expiry), Hotjar's _hjid cookie, and Amplitude's persistent device ID all qualify. Loading any of these on child sessions without verifiable parental consent disclosing the cross-site tracking is a COPPA violation. The ePrivacy Directive Article 5(3) adds a parallel EU obligation to obtain consent before setting non-essential cookies on any user, including children.
High because third-party tracking SDKs assign persistent identifiers automatically on initialization — loading them on a child session creates COPPA-covered data collection without any further developer action.
Gate all third-party analytics initialization on account type at the server side, not just in client components where timing issues can allow SDK initialization before the check completes.
// app/providers/analytics-provider.tsx
'use client'
import { useEffect } from 'react'
import { useUser } from '@/hooks/use-user'
export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
const { user } = useUser()
useEffect(() => {
// Never initialize tracking for child accounts
if (!user || user.accountType === 'child') return
initGoogleAnalytics()
initHotjar()
initAmplitude()
}, [user])
return <>{children}</>
}
Also disable third-party script loading at the Next.js layout level for child user contexts: a <Script> tag that renders before useUser() resolves can still fire. Set SameSite=Strict on all first-party session cookies for child accounts to prevent cross-site use.
ID: coppa-compliance.child-data.no-persistent-identifiers
Severity: high
What to look for: Count all relevant instances and enumerate each. COPPA's definition of "personal information" includes persistent identifiers that can be used to recognize a user over time or across sites — cookies, device IDs, IP addresses used persistently, and unique identifiers stored in browser storage or used in analytics. Examine whether the application creates or uses persistent identifiers for child accounts that could enable cross-site tracking. Check: whether cross-site tracking pixels (Facebook Pixel, Google remarketing tags) fire during child sessions, whether analytics SDKs (Amplitude, Mixpanel, Segment) assign persistent device IDs to child sessions, whether auth cookies for child accounts are scoped narrowly (no SameSite=None which enables cross-site use), and whether third-party SDKs loaded on child-accessible pages set their own persistent identifiers (the _ga, _hjid cookies set by Google Analytics and Hotjar).
Pass criteria: Child user sessions do not set or use persistent identifiers for cross-site tracking. Cross-site tracking pixels and third-party analytics SDKs that set persistent device IDs are not loaded on child-accessible pages. First-party session cookies for child accounts are appropriately scoped (SameSite=Strict or SameSite=Lax).
Fail criteria: Google Analytics, Facebook Pixel, Hotjar, or other tracking tools that set persistent cross-site identifiers load on pages accessible to child users. Analytics SDKs assign persistent device IDs to child user sessions. Auth cookies for child sessions are set with SameSite=None enabling cross-site use.
Skip (N/A) when: The application hard-blocks all users under 13 and no child sessions are possible.
Detail on fail: Example: "Google Analytics loads on all pages including those accessible to child users, setting the _ga cookie (a persistent identifier with a 2-year expiry) on child sessions." or "Amplitude analytics SDK initializes for all users, assigning a persistent device ID to child user sessions that persists across browser restarts.".
Remediation: Block third-party tracking initialization for child sessions:
// middleware.ts — check account type before rendering tracking-heavy pages
// Or in analytics initialization:
// app/providers/analytics-provider.tsx
'use client'
import { useEffect } from 'react'
import { useUser } from '@/hooks/use-user'
export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
const { user } = useUser()
useEffect(() => {
// Do NOT initialize any tracking for child accounts
if (!user || user.accountType === 'child') return
// Initialize analytics only for adult users
initAnalytics()
initHotjar()
// etc.
}, [user])
return <>{children}</>
}
Also disable third-party script loading at the layout level for child user contexts, not just in client components. A determined library may initialize itself before the user check completes in client-side code.