# 04 — Auth & login bugs > **User theme:** “I’m logged in but it keeps asking me to log in / authorize.” > **Severity:** P0 — blocks multi-server chat, voice, presence. > **Existing research:** `agents-docs/user-stories/silent-cross-signal-server-auth.md` (do not redo; implement after interview). --- ## How auth is supposed to work (user view) 1. Register or log in **once** on a home signal server. 2. Stay signed in across app restarts (desktop). 3. When touching another signal server (join, invite, create room, activate endpoint): the app **silently** creates or reuses an account there. 4. You only see a login form again if: - true home session expired / missing token, or - last-resort: username collision exhaustion / user clicked Sign in in Network settings. 5. Offline / dead endpoints never bounce you to authorize login. Settings → Network may show `Authorized` / `Needs sign-in` as diagnostics — not as the default path for normal joins. --- ## How auth is supposed to work (system) | Concept | Role | |---------|------| | Home credential | Token + user id for `homeSignalServerUrl` | | Foreign credential | Separate user id + token per URL in `SignalServerCredentialStore` | | Provision secret | Password used only for auto register/login on foreign hosts | | `ensureProvisioned` | Register-or-login with secret; suffix username on collision | | `ensureCredentialForServerUrl` | Gate before foreign room connect; navigate authorize only for `collision` or `no-provision-secret` when endpoint online | | `authorize` mode | Manual login that upserts foreign credential **without** resetting home profile | | `auth_required` vs `auth_error` | Race vs rejected token — must not falsely expire home on foreign races | Authorize nav rule: `shouldNavigateToAuthorizeSignalServer` in `signal-server-authorize.rules.ts`. --- ## Failure modes (code-backed) ### A — Missing provision secret → authorize UI (primary) **Symptom:** Logged-in user opens foreign room/invite → `/login?mode=authorize`. **Mechanism** 1. `ensureProvisioned` → `{ kind: 'skipped', reason: 'no-provision-secret' }`. 2. `ensureCredentialForServerUrl` navigates to authorize. 3. Authorize mode does not auto-leave when `currentUser` exists → stuck prompt. **Why secret missing** - Created only in `prepareAuthenticatedUserStorage` when `homeSignalServerUrl` + `loginResponse` present. - Session restore calls `ensureProvisioned` but **does not** ensure secret exists first. - Web: sessionStorage secret dies with tab. - Old installs / wiped Electron `userData/provision-secrets/`. **Files** - `signal-server-auth.service.ts`, `signal-server-authorize.service.ts` - `provision-secret-store.service.ts`, `electron/api/provision-secret-store.ts` - `users.effects.ts` ### B — Username collision exhaustion All register candidates 409 + login 401 → `collision` → authorize. Rare but real on crowded foreign servers. ### C — False home session expiry `signalServerAuthFailed$` may `SESSION_EXPIRED` → full `/login` when home classification / retry budget wrong. Distinguish `auth_required` (re-identify) vs `auth_error` (clear credential; foreign re-provision vs home expire). ### D — Credential missing → unauthenticated join → invisible user Without token, socket never identifies; `join_server` dropped; user alone; later gates open authorize. Cascades into “messages not seen” and “voice empty”. ### E — Opportunistic provision swallows errors `ensureProvisioned(...).catch(() => undefined)` on health/startup → failure deferred until mid-join authorize popup. ### F — Others? --- ## Doc / README lies in this area | Claim | Reality | |-------|---------| | Auth domain README sequence: `POST /api/auth/login` | Real paths: `/api/users/login`, `/api/users/register` (`AuthenticationService`) | | Feature doc: offline must not open authorize | Code path exists; still fails open on missing secret when “online” | --- ## Fix directions (interview choices — not approved) From user story (recommend **A**): | Option | Idea | |--------|------| | **A** | On restore / before foreign provision, always `ensureHomeProvisionSecret`; keep authorize only for collision / manual | | **B** | Never navigate on `no-provision-secret`; toast + Network badge | | **C** | Durable web secret (not sessionStorage) | | **D** | Stronger unique usernames before collision UI | --- ## Proof of done 1. Two live signal servers: home register on A → join room on B → **no** `/login` navigation. 2. Full Electron restart → foreign rejoin still silent. 3. Offline foreign URL → no authorize navigation. 4. Focused tests: missing secret on restore → secret created → provision → `Router.navigate(['/login'])` never called. 5. Foreign `auth_error` with valid home → re-provision, not `SESSION_EXPIRED`. --- ## Agent scope - Default: `toju-app/domains/authentication`, `store/users`, `store/rooms/room-signaling-connection.ts`, server-directory call sites. - Electron: provision-secret store/IPC only if persistence fix needs it. - Ask before deep `server/` auth changes.