Users under 13 blocked from accounts or routed to COPPA-compliant experience
Why it matters
COPPA §312.5 and §312.3 require that when a child is identified as under 13, no personal information is collected or stored unless verifiable parental consent is obtained. A client-side block that the server ignores is not a block — it is a UI illusion. If the account creation API processes a submission regardless of the submitted age, the operator is collecting personal information from an identified child without consent, which is a direct COPPA §312.5 violation. The FTC does not require intent; the data being stored is sufficient to establish the violation.
Severity rationale
High because a server-side bypass means personal data from identified children is being stored in the database, making every such registration a discrete COPPA §312.5 violation with per-record penalty exposure.
Remediation
Enforce the under-13 block at the API layer, not just in the form component. The route below validates age, sets a short-lived age_blocked cookie to deter immediate retry, and returns a 403 before writing any data.
// app/api/auth/signup/route.ts
import { cookies } from 'next/headers'
export async function POST(req: Request) {
const { email, password, dateOfBirth } = await req.json()
const ageYears = computeAge(new Date(dateOfBirth))
if (ageYears < 13) {
cookies().set('age_blocked', '1', {
maxAge: 60 * 60 * 24,
httpOnly: true,
sameSite: 'strict',
})
return Response.json(
{ error: 'This service requires users to be 13 or older.' },
{ status: 403 }
)
}
// ... account creation
}
If you intend to support child accounts via parental consent, implement the two-stage pending-consent flow described in the verifiable-consent check instead of hard-blocking.
Detection
-
ID:
underage-blocking -
Severity:
high -
What to look for: Count all relevant instances and enumerate each. Trace the code path that executes when a user enters a date of birth that makes them under 13. Does the code route them to a COPPA-compliant flow (parental consent workflow) or block them entirely? Check the server-side handler: does it return an error and refuse to create an account, or does it silently continue? If the application is designed to support child accounts with parental consent, look for a staging flow: a pending account state, a parent email collection step, and a block on the child's access until consent is confirmed. If the application is adults-only, verify that under-13 users are hard-blocked with a clear, non-alarmist message — not just redirected to a marketing page. Look for whether the block is persisted (session/cookie noting that access was denied) or whether a user can simply reload and try a different birth date.
-
Pass criteria: When a user identifies as under 13, one of two things happens: (1) The user is hard-blocked with a clear message that the service requires users to be 13 or older, and no account or data is created. Or (2) The user is routed to a COPPA-compliant experience that includes a verifiable parental consent flow before any account data is stored. In either case, the block or routing is enforced server-side and not bypassable by resubmitting the form with different data (e.g., rate limiting, session state). A partial or placeholder implementation does not count as pass.
-
Fail criteria: Under-13 users receive a warning message in the UI but account creation proceeds server-side anyway. The block exists client-side only and is bypassed by disabling JavaScript or altering the form submission. The under-13 path silently redirects to the homepage with no explanation and no block on re-registration attempts.
-
Skip (N/A) when: No age gate exists (in which case the
age-gate-presentcheck already fails). -
Detail on fail: Example:
"Age gate component shows an error for under-13 users in the UI, but the POST /api/auth/register endpoint does not validate the dateOfBirth field and creates the account regardless."or"Under-13 users are redirected to the homepage but no server-side flag prevents them from re-registering with a different birth date.". -
Remediation: Implement a hard block on the server side and optionally a session flag to reduce re-registration attempts:
// app/api/auth/signup/route.ts import { cookies } from 'next/headers' export async function POST(req: Request) { const { email, password, dateOfBirth } = await req.json() const birth = new Date(dateOfBirth) const now = new Date() const ageMs = now.getTime() - birth.getTime() const ageYears = ageMs / (1000 * 60 * 60 * 24 * 365.25) if (ageYears < 13) { // Set a short-lived cookie to prevent immediate retry cookies().set('age_blocked', '1', { maxAge: 60 * 60 * 24, // 24 hours httpOnly: true, sameSite: 'strict', }) // Return 403 — do NOT store email or any submitted data return Response.json( { error: 'This service is for users 13 and older. ' + 'If you believe this is an error, please ask a parent or guardian to contact us.' }, { status: 403 } ) } // ... account creation for adults }If you want to support child accounts, see the
verifiable-consentcheck for the parental consent flow. -
Cross-reference: For related patterns and deeper analysis, see the corresponding checks in other AuditBuffet audits covering this domain.
External references
- coppa · §312.5 — Verifiable parental consent — operator must not collect PI from child until consent obtained or child blocked
- coppa · §312.3 — General requirements — no collection from children under 13 without parental consent
- cwe · CWE-285 — Improper Authorization — under-13 path does not prevent account creation server-side
- owasp:2021 · A01 — Broken Access Control — underage block enforced client-side only
Taxons
History
- 2026-04-18·v1.0.0·Initial import from coppa-compliance·automated