2.8 KiB
| status | date | decision-makers | |
|---|---|---|---|
| accepted | 2026-05-03 |
|
ADR-0005: Use iron-session for authentication instead of NextAuth/Clerk/Supabase Auth
Context and Problem Statement
Scrum4Me requires username/password login without email verification, a synchronous demo-user check on every request, and full control over the session cookie shape (including an isDemo flag). Which authentication solution fits these constraints at minimal complexity?
Decision Drivers
- No email required — username/password only.
- Demo-user policy (ADR-0006) requires a synchronous
isDemocheck in both middleware and server actions. - No third-party redirect chain — auth must stay in-process.
- Solo-developer project: minimal external dependencies preferred.
Considered Options
- NextAuth / Auth.js v5
- Clerk
- Supabase Auth
- iron-session + bcryptjs
Decision Outcome
Chosen option: iron-session + bcryptjs, because it is the only option that gives us full control over cookie contents, has zero external redirect dependency, and lets us embed isDemo directly in the session payload.
Consequences
- Good, because session structure is fully controlled — we add any field we need.
- Good, because no external service dependency for auth; works offline and in CI.
- Good, because synchronous cookie read in
proxy.tsmiddleware is trivial. - Bad, because we own the password hashing, session rotation, and CSRF protection.
- Bad, because no OAuth/social login without building it ourselves.
Confirmation
lib/session.ts defines the session type. docs/patterns/iron-session.md documents the pattern. Any new field on the session object must be added to the type there.
Pros and Cons of the Options
NextAuth / Auth.js v5
- Good, because OAuth, email magic links, and credentials all in one library.
- Bad, because credentials provider is discouraged in v5; session shape is opaque.
- Bad, because adding
isDemoto the JWT requires custom callbacks.
Clerk
- Good, because fully managed, beautiful UI, no session code to maintain.
- Bad, because requires third-party redirect; adds external dependency.
- Bad, because demo-user policy would require custom session metadata sync.
Supabase Auth
- Good, because integrates with Supabase storage (but we use Neon).
- Bad, because username/password without email is not the primary use case.
- Bad, because adds a second database dependency just for auth.
iron-session + bcryptjs
- Good, because minimal, explicit, and TypeScript-native.
- Good, because session payload is a plain object we fully control.
- Neutral, because we write our own password logic (bcrypt makes it safe).
More Information
See docs/patterns/iron-session.md for implementation details. Revisit if multi-tenant or SSO requirements emerge.