Skip to content

Constant re-login on the NAS — sessions bounce to /auth/error?reason=profile-missing

Symptom

Since the app moved off Vercel onto the NAS (T-073 / T-074), the owner has to re-login constantly. Screenshot shows p-eop.theestablishers.com/auth/error?reason=profile-missing → "Something went wrong while verifying your access." Reported 2026-06-22 (verbatim):

"Ever since I started hosting the web app on my NAS, I constantly need to re-login to my web app because of reason unknown… It wasn't like this when the web app was hosted on Vercel"

Diagnosis (code-derived; confirmed against the auth flow)

It is not session expiry — it's a fail-closed RBAC re-check tripped by the NAS's flaky uplink.

  1. The NextAuth JWT callback (pages/api/auth/[...nextauth].ts) re-reads aote-system/users/{uid} every 60s (REFRESH_INTERVAL_MS) via getUserProfileStrict(uid).
  2. That reader (lib/rbac/claims.ts) throws on infra errors by design (so an infra failure isn't mistaken for a deleted profile). The callback's catch block then calls markProfileUnverified()profileMissing = true (the comment: "Fail closed: stale RBAC data is not safe…"). A transient network blip and a genuinely-deleted profile were treated identically.
  3. The next protected request hits lib/rbac/serverAuthApp.ts (if (user.profileMissing) redirect('/auth/error?reason=profile-missing')) → the screenshot.

Why the NAS and not Vercel: per docs/eop-tasks/runbooks/claude-nas-channel.md, the NAS's WAN is a 4G/CGNAT mobile modem. The every-60s Firestore round-trip over that high-latency link times out intermittently → throws → fail-closed → bounce. On Vercel the app ran in a GCP datacenter adjacent to Firestore, where this read essentially never failed, so the fail-closed branch was never exercised. Same code, very different network reliability. lib/firebaseAdmin.ts also sets preferRest: true ("optimized settings for serverless") — a Vercel-era tuning now running on a long-lived process.

Ruled out

  • Session-secret rotation: loadSecrets() falls back to a stable 'dev-secret' when NEXTAUTH_SECRET is unset, so restarts can't be silently invalidating JWTs.
  • Distinct from I-002 (client-side Firebase sessionStorage sign-in error on p-eop) — that's the browser redirect layer; this is the server-side RBAC re-read.

Not confirmable this session

Live NAS-log confirmation wasn't possible — the DSM env var held only the URL (https://nas.theestablishers.com), no account=/password=, so the DSM Web API couldn't be authenticated. Decisive log lines if creds are restored: [auth] Failed to refresh access profile (throw path) vs. a [firebaseAdmin] … applicationDefault() warning (creds/DB path).

Escalation — T-088 (the structural fix)

Escalated 2026-06-22 to T-088: make the periodic re-read tolerate a transient link failure by holding last-known-good RBAC for a bounded grace window — gated so it is self-disabling on healthy infrastructure (owner's explicit requirement). Fix specced + implemented there.

2026-07-01 — fixed (via T-088)

Resolved via T-088 (done, PR #782) — gated-resilient RBAC refresh fixed the NAS constant-relogin. [owner: close I-005] - Source (edit): EOP Local Assistance (fork) · session edb0abc7-6373-449b-8e31-379fd38d391b