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