# Phase 19: Local Auth (No-OIDC Mode) - Discussion Log

> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.

**Date:** 2026-06-16
**Phase:** 19-local-auth-no-oidc-mode
**Areas discussed:** Mode & coexistence, Session issuance, Password hashing & storage, Accounts & OIDC-link, Testing & dev-bypass

---

## Mode & Coexistence

### Q1 — How should the app decide between local-auth and OIDC?

| Option | Description | Selected |
|--------|-------------|----------|
| app_config flag (runtime) | `auth_mode` row in app_config, set by wizard; no restart | |
| Deploy-time env switch | `AUTH_MODE` env read at boot | |
| Both always live | Local form + OIDC button always shown | |

**User's choice:** Free-text — "Default to local and add the ability to wire OIDC in later if wanted."
**Notes:** Local is the always-available default; OIDC is additive/opt-in.

### Q2 — How does the app know OIDC is wired in, and what happens to local login?

| Option | Description | Selected |
|--------|-------------|----------|
| Auto-detect, local stays live | OIDC on when config present; local always available | (partial) |
| Auto-detect, OIDC takes over | Local disabled once OIDC present | |
| Explicit app_config toggle | Separate `auth_mode` controlled from admin UI | (partial) |

**User's choice:** Free-text — OIDC config is set/stored in a later step, so wire it into the **admin UI**; give users the choice of which to use at login; **no local login UI exists today** so it must be built.
**Notes:** Blend — admin-UI-configured OIDC, both methods offered at login, user chooses.

---

## Session Issuance

### Q1 — What backs a local login session?

| Option | Description | Selected |
|--------|-------------|----------|
| Stateless signed JWT cookie | userId in signed httpOnly cookie; no DB table | ✓ (Claude) |
| Server-side session table | sessions table for true revocation | |
| You decide | — | ✓ |

**User's choice:** "You decide" + "do not pigeon-hole the user into Authelia — Bring Your Own Auth and Bring Your Own CalDAV provider."
**Notes:** Claude chose stateless signed JWT cookie. User added the BYO-Auth architectural principle (generic OIDC, not Authelia-locked).

### Q2 — How far should the BYO-Auth abstraction go in Phase 19?

| Option | Description | Selected |
|--------|-------------|----------|
| Local + generic OIDC | Two concrete methods, clean seam, no framework | ✓ |
| Full pluggable framework | Provider registry/plugin (LDAP, magic-link, multi-OIDC) | |
| Local only for now | Leave Authelia OIDC as-is, defer generic OIDC | |

**User's choice:** Local + generic OIDC (recommended).
**Notes:** Clean internal seam now; full framework deferred.

---

## Password Hashing & Storage

### Q1 — Which password hashing approach?

| Option | Description | Selected |
|--------|-------------|----------|
| scrypt via node:crypto | Stdlib, zero-dep, no native build | ✓ |
| argon2id (native dep) | OWASP top pick, needs native addon | |
| bcrypt (bcryptjs) | Pure JS, older KDF | |

**User's choice:** scrypt via node:crypto (recommended).
**Notes:** Honors the stack's no-native-dep stance.

### Q2 — Where to store username + hash?

| Option | Description | Selected |
|--------|-------------|----------|
| Separate local_credentials table | Mirrors member_credentials; user-agnostic users row | ✓ (Claude) |
| Columns on users | Add username + password_hash to users | |
| You decide | — | ✓ |

**User's choice:** "You decide."
**Notes:** Claude chose a separate `local_credentials` table — best fits the BYO-Auth per-user-method seam (one row can hold both a local credential and an OIDC binding).

---

## Accounts & OIDC-Link

### Q1 — How are local accounts created?

| Option | Description | Selected |
|--------|-------------|----------|
| Admin creates members | Wizard creates first admin; admin creates rest | ✓ |
| Admin creates + invite link | One-time set-password link | |
| Open self-signup | Anyone can register | |

**User's choice:** Admin creates members.

### Q2 — Password change/reset?

| Option | Description | Selected |
|--------|-------------|----------|
| Self-change + admin reset | Member self-change; admin resets lockouts | ✓ |
| Self-change only | No admin reset | |
| Admin reset only | No self-change | |

**User's choice:** Self-change + admin reset.
**Notes:** No email reset (email out of scope).

### Q3 — After OIDC-link, what methods stay valid? (reformulated after clarification)

| Option | Description | Selected |
|--------|-------------|----------|
| Both stay valid | Row holds local + OIDC; either logs in | |
| OIDC primary, local fallback | Same data model, UI emphasis on OIDC | |
| OIDC replaces local | Linking removes local credential | ✓ (per user) |

**User's choice:** Initially requested clarification; then chose **OIDC replaces local per user** — there can/should be OIDC-only users with no local creds. Raised the need for a break-glass path.
**Notes:** Auth methods are per-user (presence of local_credentials row and/or OIDC binding). Break-glass need surfaced here.

### Q4 — Break-glass capability model?

| Option | Description | Selected |
|--------|-------------|----------|
| Protected local admin (no new role model) | Initial admin, un-removable local cred | |
| Operator-only account (member/operator split) | Strip member capability from break-glass | |
| Let researcher scope it | Lock the requirement, defer the how | ✓ (twist) |

**User's choice:** Let researcher scope it — **with a twist: break-glass can be a CLI/console command or env override instead of a user**, removing the added-user/capability complexity.
**Notes:** No new role/capability model; reuse `is_admin`. Recovery mechanism (not account) to be scoped by researcher.

---

## Testing & Dev-Bypass (added mid-discussion at user's request)

### Q1 — How should DEV_AUTH_BYPASS evolve?

| Option | Description | Selected |
|--------|-------------|----------|
| Bypass stays + seed a real test login | Fast bypass for most specs; real form for login specs | |
| Replace bypass with seeded auto-login | Harness logs in via real local flow | (user's lean) |
| Bypass auto-issues a real local session | Bypass logs in seeded user, skips form | |

**User's choice:** Defer final determination to the **research agent**; user **leans toward "replace bypass with seeded auto-login."**
**Notes:** Hard constraint — the seeded test login / dev-bypass **stays dev-only and never ships in the Docker/prod image** (Phase 16 IMG-01/02/03 gates apply).

---

## Claude's Discretion

- Local session backing → stateless signed JWT cookie (D-05).
- Credential storage location → separate `local_credentials` table (D-09).

## Deferred Ideas

- Full pluggable auth-provider framework (registry/plugin; LDAP, magic-link, multi-OIDC) — future phase, counterpart of 999.1.
- Member-vs-operator capability/role split — rejected in favor of CLI/env break-glass recovery.
- Email-based password reset — out of project scope.
