COPPA §312.5 requires operators to obtain verifiable parental consent before collecting personal information from children under 13. Without a server-enforced age gate, your registration endpoint creates accounts and stores personal data — name, email, date of birth — for users who may be children, with no parental involvement. The FTC treats this as a per-se violation regardless of whether you intended to attract children. Civil penalties reached $170 million in FTC v. Google/YouTube and $5.7 million in FTC v. Musical.ly. A client-side-only age gate provides zero legal protection because it is trivially bypassed by altering the form submission.
Critical because any personal data stored before age verification makes the operator liable under COPPA §312.5 the moment a child completes registration — there is no cure once the data exists.
Add server-side age validation to every account creation and data collection endpoint before committing any data to the database. The component below collects date of birth neutrally and the API route rejects submissions from users under 13 at the server level.
// app/api/auth/signup/route.ts
export async function POST(req: Request) {
const { email, password, dateOfBirth } = await req.json()
const age = computeAge(new Date(dateOfBirth))
if (age < 13) {
// Return 403 before writing any data
return Response.json({ error: 'Age requirement not met.' }, { status: 403 })
}
// ... proceed with account creation
}
Do not rely on ageVerified: true flags sent from the client — always compute age from the submitted birth date on the server.
ID: coppa-compliance.age-determination.age-gate-present
Severity: critical
What to look for: Count all relevant instances and enumerate each. Before evaluating, extract and quote any relevant configuration or UI text found. Examine the registration and account creation flow from start to finish. Is there any point at which the user's age is verified or collected before the account is created and personal information is stored? Look for date-of-birth fields, age input fields, "I am over 13" checkboxes, or age confirmation modals. The gate must appear before personal information is collected and stored — not after the account already exists. Check that the age check is present in the server-side account creation handler, not just in a client-side form component (client-only age gates are trivially bypassed). If the application has no registration flow (anonymous or fully public), look for whether any data collection points — newsletter signups, contact forms, comment sections, quiz tools — have an age gate before accepting submissions.
Pass criteria: An age verification step exists that runs before any personal information is collected and stored. The verification appears in every registration or data collection entry point. Server-side enforcement confirms the age gate cannot be bypassed by modifying the frontend. A partial or placeholder implementation does not count as pass. Report the count even on pass.
Fail criteria: No age gate of any kind exists on any registration or data collection form. Or an age gate exists in the UI component but is not enforced server-side — the API endpoint accepts submissions regardless of age data. Or the age gate appears after account creation (too late).
Skip (N/A) when: The application collects absolutely no personal information from any user — no name, no email, no account, no analytics with persistent identifiers. Purely anonymous, stateless, no-form applications only.
Detail on fail: Specify what is missing or where the gap is. Example: "No age gate found on registration form. Account creation API endpoint accepts any email with no age verification step." or "Age gate component present in signup UI but age value is not validated server-side; API creates accounts regardless of submitted age.".
Remediation: Implement an age gate that is enforced server-side at the point of data collection. A date-of-birth gate is stronger than a checkbox because it cannot be bypassed by simply lying without entering a false date:
// components/AgeGate.tsx — present before collecting any other data
'use client'
import { useState } from 'react'
interface AgeGateProps {
onAdult: () => void
onChild: () => void
}
export function AgeGate({ onAdult, onChild }: AgeGateProps) {
const [dob, setDob] = useState('')
const [error, setError] = useState('')
function handleSubmit(e: React.FormEvent) {
e.preventDefault()
const birth = new Date(dob)
const now = new Date()
const age = now.getFullYear() - birth.getFullYear() -
(now < new Date(now.getFullYear(), birth.getMonth(), birth.getDate()) ? 1 : 0)
if (isNaN(birth.getTime())) {
setError('Please enter a valid date of birth.')
return
}
// Route based on age: do NOT store age in localStorage (bypassable)
if (age >= 13) {
onAdult()
} else {
onChild() // Redirect to COPPA-compliant flow or block
}
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="dob">Date of birth</label>
<input
id="dob"
type="date"
value={dob}
onChange={e => setDob(e.target.value)}
max={new Date().toISOString().split('T')[0]}
required
/>
{error && <p role="alert">{error}</p>}
<button type="submit">Continue</button>
</form>
)
}
Server-side enforcement in the account creation handler:
// app/api/auth/signup/route.ts
export async function POST(req: Request) {
const { email, password, dateOfBirth } = await req.json()
const birth = new Date(dateOfBirth)
const now = new Date()
const age = now.getFullYear() - birth.getFullYear()
if (age < 13) {
// Do NOT create the account. Do NOT store the email.
return Response.json(
{ error: 'Users under 13 require parental consent. See our COPPA flow.' },
{ status: 403 }
)
}
// Proceed with account creation for adults
}
Cross-reference: For related patterns and deeper analysis, see the corresponding checks in other AuditBuffet audits covering this domain.