--- status: accepted date: 2026-05-03 decision-makers: [janpetervisser] --- # 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 `isDemo` check 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.ts` middleware 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 `isDemo` to 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.