Step-by-Step Guide — Follow each step exactly as written. All values are provided — do not improvise.

TC-AUTH-006: Session Renewal & Refresh-Token Rotation (Detailed)

Module: Authentication & Identity — Session Renewal (org + platform portals)

Org Test User: admin@rcme.membervu.com / Admin123!

Platform Test User: superadmin@zeniark.com / SuperAdmin123!

Priority: P1

URLs for this test:
Org portal: https://stg-rcme.membervu.com/login
Admin/platform portal: https://stg-admin.membervu.com
API: https://stg-api.membervu.com
Run this on staging, not local dev. Local dev's access-token TTL is overridden to 60 minutes; staging uses the documented 15-minute TTL. If you must approximate on local dev, treat "60 minutes" as your wait window instead of 15, and note in your report that the run was on local dev.

Step 1: Org-portal session survives past the access-token TTL (AU6-01)

StepAction (EXACT clicks/typing)Expected Result (EXACT text/behavior)Test Value
1Go to https://stg-rcme.membervu.com/login; sign in.Dashboard loads.admin@rcme.membervu.com / Admin123!
2Open DevTools → Network tab. Leave the tab open and idle for at least 16 minutes (past the 15-min access-token TTL).No action needed during the wait — the tab just sits idle.Wait ≥16 min
3After the wait, click any navigation link (e.g. Members).The page loads normally — you are NOT redirected to login. In the Network tab you should see a POST /api/auth/refresh call return 200 shortly before or during this navigation (the client refreshes proactively/on-demand).-
4Inspect the /api/auth/refresh response.Returns { success: true, token: "…", ... } — a NEW access token, different from the one issued at login.-
Data assertion: the new access token from Step 4 must differ from the original login token (rotation happened), and the UI must show no interruption — no login redirect, no error toast.

Step 2: Admin-portal session survives past the access-token TTL (AU6-02)

StepActionExpected ResultTest Value
1In a separate browser (or private window), go to https://stg-admin.membervu.com; sign in.Platform dashboard loads.superadmin@zeniark.com / SuperAdmin123!
2Open DevTools → Network tab. Leave idle for at least 16 minutes.-Wait ≥16 min
3Click any admin-portal navigation link (e.g. Tenants).Loads normally, no forced logout. A POST /api/auth/platform/refresh call (NOT /api/auth/refresh) appears in the Network tab returning 200.-
4Inspect the response.{ success: true, token: "…", tenant_id: null, ... } — new access token, tenant_id is null (platform admin, not tenant-bound).-
Data assertion: the admin portal must call the dedicated /api/auth/platform/refresh endpoint, not the org /api/auth/refresh — confirm the exact request URL in DevTools.

Step 3: Two-tab logout (AU6-03)

StepActionExpected ResultTest Value
1In Tab 1, log in to the org portal: https://stg-rcme.membervu.com/login.Dashboard loads.admin@rcme.membervu.com / Admin123!
2Open Tab 2 in the same browser, navigate to https://stg-rcme.membervu.com/dashboard.Tab 2 is also authenticated (shared localStorage).-
3In Tab 1, click Logout.Tab 1 redirects to login. The refresh-token chain is revoked server-side (POST /api/auth/logout).-
4Switch to Tab 2. Click any navigation link, or wait for its next background refresh cycle, then trigger a request.Tab 2 loses access on its next action — either an immediate redirect to login (BroadcastChannel-coordinated logout) or, at the latest, on its next /api/auth/refresh call which now returns 401 (the token was revoked in Step 3). Tab 2 must NOT continue functioning indefinitely as if nothing happened.-

Step 4: Password reset revokes all refresh tokens (AU6-04)

StepActionExpected ResultTest Value
1Using a throwaway test member (not a shared seed account), log in on Device/Browser A.Session A active.Disposable test member
2On Device/Browser B, use "Forgot password" for the same account, complete the reset with a new password (check MailHog for the reset link).Password reset succeeds; you can log in with the new password on Device B.MailHog: https://stg-webmail.membervu.com
3Back on Device A (still holding the pre-reset session), trigger any action after the access token would need to refresh (or directly attempt a refresh by waiting past the TTL, or clearing the access token from localStorage to force a refresh attempt).Device A's session is rejected — the refresh cookie tied to the pre-reset session no longer works (401 on /api/auth/refresh); Device A is forced to log in again with the NEW password.-
Data assertion: Device A cannot silently continue using the old session after the password reset on Device B — this proves password-change calls the revoke-all-refresh-tokens path, not just a per-device revoke.

Step 5: Replayed (already-rotated) refresh token rejected (AU6-05)

This is a security assertion. Single-use rotation means a captured/stolen refresh token becomes useless the moment it's used once — replaying it must NOT mint a second access token.
StepActionExpected ResultTest Value
1Log in as admin@rcme.membervu.com / Admin123!. Open DevTools → Application/Storage → Cookies, and copy the value of the refresh_token cookie.You have the plaintext refresh-token JWT string.Copy the refresh_token cookie value
2Trigger a refresh once (e.g. via DevTools console: fetch('/api/auth/refresh', {method:'POST', credentials:'include'}), or wait for a natural refresh).200 — a new access token is issued and the refresh_token cookie is rotated to a NEW value (old value is now consumed/rotated server-side).-
3Using a REST client (curl/Postman) or by manually re-setting the cookie to the OLD value copied in Step 1, call POST https://stg-api.membervu.com/api/auth/refresh presenting the OLD (already-rotated) refresh token.401 Unauthorized{ error: { code: "UNAUTHORIZED", message: "Invalid refresh token" } }. The old token does NOT mint a new access token.Old refresh_token value from Step 1

Step 6: Cross-portal cookie isolation (AU6-06 / AU6-07, #539/#540)

StepActionExpected ResultTest Value
1In one browser, open two tabs: Tab A → https://stg-rcme.membervu.com/login, log in as admin@rcme.membervu.com / Admin123!. Tab B → https://stg-admin.membervu.com, log in as superadmin@zeniark.com / SuperAdmin123!.Both tabs are independently authenticated.Org: admin@rcme.membervu.com / Admin123!
Platform: superadmin@zeniark.com / SuperAdmin123!
2Open DevTools → Application → Cookies in either tab (shared jar for the domain).You see two distinct refresh cookies: refresh_token (org) and platform_refresh_token (platform) — coexisting, neither overwritten.-
3In Tab A (org portal), click Logout.Tab A is logged out. Switch to Tab B (admin portal) and perform any admin action (e.g. view Tenants list).-
4Verify Tab B (admin portal).Tab B is still fully logged in and functional — the org logout did NOT affect the platform session. This is the #539 isolation guarantee.-
5Log back into the org portal in Tab A (so both an org auth_token cookie and a valid platform Bearer/session coexist). In Tab B, perform a privileged admin action (e.g. edit a tenant setting).The admin action succeeds with no unexpected 403 — even though an org auth_token cookie is present in the same browser, authMiddleware prefers the admin portal's explicit Authorization: Bearer header (#540) over the stale/unrelated org cookie.-

CLEANUP