Skip to content

fix: demo logins (acme/globex) on Aspire launch + expired-token session restore#1260

Merged
iammukeshm merged 2 commits into
mainfrom
fix/demo-login-and-session-restore
May 27, 2026
Merged

fix: demo logins (acme/globex) on Aspire launch + expired-token session restore#1260
iammukeshm merged 2 commits into
mainfrom
fix/demo-login-and-session-restore

Conversation

@iammukeshm
Copy link
Copy Markdown
Member

Why

On a fresh dotnet run --project src/Host/FSH.Starter.AppHost, logging into the dashboard with the advertised demo accounts (admin@acme.com / Password123!) failed in every browser, including incognito. Two independent defects:

  1. Demo tenants were never seeded. AppHost ran only the migrator's apply --seed verb (root tenant/admin only). The acme/globex demo tenants are created solely by the dev-only seed-demo verb, which AppHost never invoked — so the demo-login panel advertised accounts that didn't exist.
  2. A stale/expired token booted the app as "signed in." Both React apps derived isAuthenticated from token presence/decodability without checking exp, so a leftover token in localStorage rendered the app and fired protected requests that 401'd in a loop (SecurityTokenExpiredException) instead of refreshing or routing to /login.

What changed

Backend / scaffold

  • AppHost: new dev-only fsh-demo-seeder resource runs seed-demo after the base migration; the API waits for it so demo accounts exist before the dashboard is reachable. Idempotent. Pins DOTNET_ENVIRONMENT=Development (the generic-host migrator ignores Aspire's injected ASPNETCORE_ENVIRONMENT) and passes Seed__DemoPassword explicitly.
  • DbMigrator: disable build-time DI validation. The migrator runs a deliberately reduced service graph (Mailing/SignalR/Jobs off); Development's auto-on ValidateOnBuild walked request handlers the migrator never invokes (needing IHubContext/IMailService) and crashed startup. The migrator only resolves migration/seed services, so this validation was a false positive (no-op in Production).

Frontend (admin + dashboard, symmetric)

  • isTokenExpired added to jwt.ts.
  • Auth context now does a one-shot silent refresh at boot when the access token is missing/expired but a refresh token is present (isInitializing), deciding signed-in vs /login before rendering. A live session whose access token expires mid-use stays authenticated (reactive 401-refresh), so the 45-min access / 7-day refresh UX is preserved.
  • ProtectedRoute renders a loader while initializing so no request fires with a dead token.
  • Exported refreshAccessToken for the boot path.

Tests

  • New tests/auth/session-restore.spec.ts in both apps: expired token + refresh-fails → routes to /login; expired token + refresh-succeeds → session restored, no /login bounce. 4/4 pass.
  • tsc -b clean for both apps; eslint 0 errors; backend dotnet build clean.

Verification

Relaunching Aspire seeds acme/globex and demo logins succeed; a stale token now silently refreshes or cleanly redirects to /login instead of dumping SecurityTokenExpiredException.

🤖 Generated with Claude Code

iammukeshm and others added 2 commits May 27, 2026 23:20
The dashboard's demo-login panel advertises acme/globex accounts, but
AppHost only ran the migrator's `apply --seed` verb, which provisions the
root tenant/admin only. The acme/globex demo tenants are created
exclusively by the dev-only `seed-demo` verb, which AppHost never invoked
- so every acme/globex login failed against a freshly-provisioned DB.

- AppHost: add a dev-only `fsh-demo-seeder` resource that runs `seed-demo`
  after the base migration; the API waits on it so the demo accounts exist
  before the dashboard is reachable. Pin DOTNET_ENVIRONMENT=Development
  (the generic-host migrator ignores Aspire's injected
  ASPNETCORE_ENVIRONMENT) and pass Seed__DemoPassword explicitly.
- DbMigrator: disable build-time DI validation. The migrator runs a
  reduced service graph (Mailing/SignalR/Jobs disabled); Development's
  auto-on ValidateOnBuild walked request handlers the migrator never
  invokes (needing IHubContext/IMailService) and crashed startup. The
  migrator only resolves migration/seed services.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both apps derived isAuthenticated from token presence/decodability and
never checked `exp`. A stale/expired token in localStorage booted straight
into the app, firing protected requests that 401'd in a loop (the
SecurityTokenExpiredException bursts) instead of refreshing or routing to
/login.

- jwt.ts: add isTokenExpired (with a small clock-skew margin).
- auth-context: on boot, if the access token is missing/expired but a
  refresh token is present, run one silent refresh before deciding
  signed-in vs /login (new isInitializing flag). A live session whose
  access token expires mid-use stays authenticated (reactive 401-refresh
  handles it) so long-lived sessions are preserved.
- protected-route: render a loader while initializing so no protected
  request fires with a dead token.
- api-client: export refreshAccessToken for the boot path.
- tests: session-restore specs for both apps (refresh-fails -> /login;
  refresh-succeeds -> session restored, no /login bounce).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@iammukeshm iammukeshm merged commit 727c76e into main May 27, 2026
12 checks passed
@iammukeshm iammukeshm deleted the fix/demo-login-and-session-restore branch May 27, 2026 17:52
iammukeshm added a commit to fullstackhero/docs that referenced this pull request May 27, 2026
- changelog: entries for the demo-seed wiring (new fsh-demo-seeder resource)
  and the expired-token session-restore fix in both React apps.
- aspire / database-migrations / introduction: document the fsh-demo-seeder
  resource and that Aspire auto-runs seed-demo on launch; correct the
  seed-demo env gate to DOTNET_ENVIRONMENT (the migrator is a generic-host
  console app, not a web host that reads ASPNETCORE_ENVIRONMENT).

Tracks fullstackhero/dotnet-starter-kit#1260.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
iammukeshm added a commit that referenced this pull request May 27, 2026
The demo seeder realigns every tenant admin -- including root's admin@root.com
-- to the shared demo password, so under the Aspire dev stack the root account
signs in with Password123!, not the framework default 123Pa$$word!. Surfaced by
running seed-demo on launch (PR #1260). Point the demo login panel (ROOT_PASSWORD)
and the sample token request at Password123! so the advertised dev credential
actually works. The framework seed default (Seed:DefaultAdminPassword) is
unchanged for non-demo / production seeds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pull Bot pushed a commit to NicoJuicy/dotnet-webapi-boilerplate that referenced this pull request May 27, 2026
The demo seeder realigns every tenant admin -- including root's admin@root.com
-- to the shared demo password, so under the Aspire dev stack the root account
signs in with Password123!, not the framework default 123Pa$$word!. Surfaced by
running seed-demo on launch (PR fullstackhero#1260). Point the demo login panel (ROOT_PASSWORD)
and the sample token request at Password123! so the advertised dev credential
actually works. The framework seed default (Seed:DefaultAdminPassword) is
unchanged for non-demo / production seeds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant