# Users under 13 blocked from accounts or routed to COPPA-compliant experience

- **Pattern:** `ab-000815` (`coppa-compliance.age-determination.underage-blocking`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000815
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000815)

## 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.

```ts
// 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-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:

  ```ts
  // 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.

---

## External references

- coppa §312.5
- coppa §312.3
- cwe CWE-285
- owasp A01

Taxons: regulatory-conformance, access-control

HTML version: https://auditbuffet.com/patterns/ab-000815
