Module 4 · Authentication and security

Lesson 23 — Secrets, rate limiting and GDPR

Secret management, request limits and personal-data protection.

Published
In this lesson
  1. Objectives
  2. 1. Secrets: the life cycle
  3. 2. Rate limiting: token bucket with Redis
  4. 3. GDPR for the developer (what touches your code)
  5. 4. Audit logs (the stitch that ties it together)
  6. Self-assessment

Stack: DRF + Redis · Project: TicketFlow Status: Published — closing the security module Prerequisite: Lesson 22 — OWASP


Objectives

  1. Manage secrets without them ending up in Git, logs or Docker images (environment hierarchy and rotation).
  2. Implement real rate limiting (token bucket with Redis) on login and checkout.
  3. Apply GDPR to TicketFlow: legal basis, minimization, rights (export/deletion) and audit logs.

1. Secrets: the life cycle

  • Never in Git (05): .env out, env.example in. A leaked history is repaired by rotating, not deleting.
  • Environment hierarchy: dev (dummy values), staging (test secrets), prod (KMS/vault: AWS Secrets Manager, HashiCorp Vault, or at minimum: the orchestrator's environment variables — never baked into the Docker image: the image leaks).
  • Rotation: every credential has an expiry date in your inventory (Django's SECRET_KEY: rotate with a session migration plan; gateway keys: dual rotation — publish the new one, migrate, revoke the old one). Successful rotation is the kind you can do without downtime: design for that.
  • People's access: nobody keeps prod secrets on their machine; temporary, audited access (25/56). git-secrets/trufflehog in CI to prevent secret commits.

2. Rate limiting: token bucket with Redis

The limit protects: login (brute force, 20), checkout (bots buying everything — TicketFlow's I4-adjacent case), the public API (abuse). Token bucket algorithm: the user has N tokens regenerating at rate R; every request spends one; without a token → 429 with Retry-After.

python
def allow(key: str, capacity: int, refill_per_min: int) -> bool:
    now = time.time()
    pipe = redis_pipeline()
    data = pipe.hgetall(key).execute()          # tokens, last_refill
    tokens = min(capacity, float(data.get(b"tokens", capacity)))
    elapsed = now - float(data.get(b"last", now))
    tokens = min(capacity, tokens + elapsed * refill_per_min / 60)
    if tokens < 1:
        pipe.hset(key, mapping={"tokens": tokens, "last": now}); pipe.expire(key, 3600)
        return False
    pipe.hset(key, mapping={"tokens": tokens - 1, "last": now}); pipe.expire(key, 3600)
    return True

Production: make it atomic with a Lua script (avoids the race between HGETALL and HSET — 10/12 applied to Redis) or use the django-ratelimit library. Keys: by IP for anonymous users, by user for authenticated ones, by sensitive endpoint (login, checkout) with harder limits. And an honest response: 429 + Retry-After + a log of the event (46): a blocked bot is information, not noise.

3. GDPR for the developer (what touches your code)

  • Legal basis and minimization: only data needed to sell tickets: name, email, and billing data. The "date of birth just in case" is NOT collected. Less data = less liability.
  • Purpose and retention: reservations are retained (fiscal), abandoned carts are NOT: a purge job for old expired (anonymous) PENDING ones — retention is a data decision, documented.
  • Right of access/export (Art. 15): a user export endpoint (their profile + reservations + payments, JSON or CSV) — authenticated, their own data, 14 guarantees the export's idempotency.
  • Right to be forgotten (Art. 17): delete/no: sales carry a fiscal obligation → anonymization (email → irreversible hash, name → "deleted"), keep the accounting record. Never DELETE payments (audit, 08).
  • Consent: marketing separate from operations (a non-mandatory checkbox), revocable in one click.
  • Processors: the gateway and the email provider are data processors: a signed DPA (the business's part; your part: knowing what data travels to each one).

4. Audit logs (the stitch that ties it together)

The audit log records WHO did WHAT WHEN on which resource, immutable: role changes (21), refunds, event changes, user-data access. An append-only table (no UPDATE/DELETE by design) + defined retention. It is what turns "we believe nobody touched the payment" into "here is the record" (56 formalizes it for SOC2).


Self-assessment

  1. Your repo leaked a gateway secret a month ago: what is the only real repair, and how is it done without downtime?
  2. Why does the token bucket need atomicity (Lua), and what bug does the version with separate HGET/HSET produce?
  3. Why is the login rate limit per user+IP and not just per IP? Which attack does the per-user limit prevent?
  4. A user asks to be deleted: why not DELETE their payments, and what exactly is done with their data?
  5. Which three pieces of data must TicketFlow NOT ask for, and why (minimization)?

Continue with the exercises. The solutions only after trying it yourself.