# Store Login: JWT Token Gets Wrong Platform **Status:** Resolved **Date:** 2026-02-24 **Resolved:** 2026-03-10 ## Problem When a user logs in to a store via `/platforms/loyalty/store/FASHIONHUB/login`, the JWT token should encode `platform_id=3` (loyalty). Instead, it gets `platform_id=2` (main). This causes the store sidebar menu to show the wrong modules — e.g., analytics (from "main") appears while loyalty (from "loyalty") is missing. ### Root Cause Chain 1. User visits `http://localhost:8000/platforms/loyalty/store/FASHIONHUB/login` 2. PlatformContextMiddleware detects `loyalty` and rewrites path to `/store/FASHIONHUB/login` — **correct** 3. Login page renders with loyalty platform context — **correct** 4. JavaScript `login.js` POSTs to `apiClient.post('/store/auth/login', ...)` which resolves to `/api/v1/store/auth/login` — **no platform prefix** 5. PlatformContextMiddleware sees `/api/v1/store/auth/login`, doesn't match `/platforms/*`, defaults to "main" platform 6. Store auth endpoint calls `get_current_platform(request)` → gets "main" (id=2) instead of "loyalty" (id=3) 7. Token encodes `platform_id=2`, all subsequent menu/API calls use the wrong platform ## Solution (Implemented) The login endpoint uses a 3-source priority chain to resolve the platform: | Source | How | When it fires | |--------|-----|---------------| | **Source 1: Middleware** | `request.state.platform` from domain/subdomain/custom-domain | **Production always** — domain carries platform context in every request | | **Source 2: Request body** | `platform_code` field in login JSON body | **Dev mode** — JS sends `window.STORE_PLATFORM_CODE \|\| localStorage.store_platform` | | **Source 3: Fallback** | `get_first_active_platform_id_for_store()` | **Only** on fresh browser in dev mode (no URL context, no localStorage) | ### Files Changed | File | Change | |------|--------| | `app/modules/tenancy/routes/api/store_auth.py` | Added `platform_code` to `StoreLoginResponse` and `/me` response | | `app/modules/tenancy/schemas/auth.py` | Added `platform_code` to `StoreUserResponse` | | `app/modules/tenancy/static/store/js/login.js` | Save `platform_code` to localStorage on login; use as fallback in login request | ### Why Source 3 Fallback Is Safe Source 3 only fires when **both** Source 1 and Source 2 have nothing — meaning: - Not on a platform domain (localhost without `/platforms/` prefix) - No `platform_code` in request body (no `STORE_PLATFORM_CODE` on page, no localStorage) This only happens on a completely fresh browser session in dev mode. In production, Source 1 always resolves because the domain itself identifies the platform. ### Platform Resolution by URL Pattern See [middleware.md](../architecture/middleware.md) § "Login Platform Resolution" for the complete matrix. ## Diagnostic Tools - **Backend trace**: `/admin/platform-debug` — simulates the full middleware + login resolution pipeline for any host/path combo - **JS overlay**: `Ctrl+Shift+P` on any store page (localhost only) — shows `window.STORE_PLATFORM_CODE`, `localStorage.store_platform`, JWT decoded platform, `/auth/me` response, and consistency checks