Status: Accepted — May 2026
Scope: django-backend/accounts/ HTTP surface, frontend/ /register route, and how registration differs from login for duplicate-email signaling.
M2 shipped session login via POST /api/auth/login/ and createsuperuser for the first admin. New sandbox users still had to be created out-of-band (admin or shell), which blocks demos and early testers.
We keep Django sessions (no JWT), the email-based User model, and the same cookie + X-CSRFToken contract as login. Registration must not grant is_staff / is_superuser from the client.
-
Single endpoint:
POST /api/auth/register/with bodyemail,password,password_confirm(snake_case JSON matches DRF field names). -
Validation: reuse Django
AUTH_PASSWORD_VALIDATORSviavalidate_passwordon a transientUser(email=…)instance; requirepassword == password_confirmin the serializervalidate()method. -
Persistence:
User.objects.create_user(email, password)only — never rawUser.objects.create. Normalize email withUser.objects.normalize_emailinside the serializer so storage matchescreate_user. -
Session: on success, call
django_login(request, user)immediately (same fixation benefit as login). Response201 Createdwith the same JSON shape asPOST /api/auth/login/(UserSerializer). -
Duplicate email: return
409 Conflictwith{"code": "EmailAlreadyRegistered", "detail": "…"}. This intentionally differs from login’s generic401for wrong credentials — registration cannot hide that an email is already taken. -
Concurrency: catch
IntegrityErroraroundcreate_useras a second line of defense after anexists()check (race-safe). -
Out of scope for this ADR: email verification, password reset, rate limiting, captcha,
Profilemodel.
- Frontend adds
/register,RegisterForm, anduseAuth().register(...). docs/api-contracts.mddocuments the new endpoint; compliance notes mention open registration in sandbox.- CI gains
accountstests using DjangoClient(CSRF header parity is documented where DRF’scsrf_exemptwrapper applies).
docs/decisions/0005-cross-origin-session-spa.md— CSRF + CORS for unsafe POSTs.docs/api-contracts.md— request/response matrix.AGENTS.md— session auth remains the transport; no client-trusted balances.