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.
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.
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.
ID: coppa-compliance.age-determination.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-present check 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-consent check for the parental consent flow.
Cross-reference: For related patterns and deeper analysis, see the corresponding checks in other AuditBuffet audits covering this domain.