# Logic Spec: Fix 401 Unauthorized on Admin Ledger Endpoint **Date:** 2026-07-25 **Severity:** P0 (Blocker — admin users cannot access Financial Ledger page) **Related Issue:** Frontend redirects to login screen on `/finance/ledger` --- ## 1. Root Cause Analysis ### 1.1 Authentication Architecture The FastAPI backend uses `OAuth2PasswordBearer` ([`deps.py:27-29`](backend/app/api/deps.py:27)) which extracts the JWT token **exclusively** from the `Authorization: Bearer ` HTTP header. It does **NOT** read tokens from cookies. ```python # backend/app/api/deps.py:27-29 reusable_oauth2 = OAuth2PasswordBearer( tokenUrl=f"{settings.API_V1_STR}/auth/login" ) ``` The auth dependency chain for the ledger endpoint: ``` GET /admin/finance/ledger → RequirePermission("finance:view") [deps.py:283] → get_current_active_user [deps.py:94] → get_current_user [deps.py:56] → get_current_token_payload [deps.py:31] → reusable_oauth2 (OAuth2PasswordBearer) [deps.py:27] → extracts token from Authorization: Bearer header ``` ### 1.2 The Bug The [`ledger.vue:348`](frontend_admin/pages/finance/ledger.vue:348) uses **native `fetch()`**: ```typescript const res = await fetch(`/api/v1/admin/finance/ledger?${params.toString()}`, { credentials: 'include', }) ``` This sends cookies (including `access_token` cookie) but does **NOT** send the `Authorization: Bearer` header. The `OAuth2PasswordBearer` dependency never sees a token → 401 Unauthorized. ### 1.3 Why Other Admin Pages Work Every other admin page uses `$fetch()` (Nuxt's ofetch wrapper) with a `getHeaders()` helper that explicitly adds the `Authorization` header: ```typescript function getHeaders(): Record { const tokenCookie = useCookie('access_token') return tokenCookie.value ? { Authorization: `Bearer ${tokenCookie.value}` } : {} } ``` Examples: - [`commission-rules.vue:694`](frontend_admin/pages/finance/commission-rules.vue:694) — has `getHeaders()` - [`providers/index.vue:288`](frontend_admin/pages/providers/index.vue:288) — has `getHeaders()` - [`gamification/ledger.vue:244`](frontend_admin/pages/gamification/ledger.vue:244) — has `getHeaders()` The `ledger.vue` page **omitted** both `$fetch` and the `getHeaders()` helper — it's the only admin page with this pattern. ### 1.4 What the 401 Interceptor Does The [`api.ts` plugin](frontend_admin/plugins/api.ts:5) overrides `globalThis.fetch` to detect 401 responses. When a 401 is detected on any non-login URL, it calls `authStore.logout()` and redirects to `/login`. This is why the user sees a redirect — the interceptor fires, clears the token, and forces a redirect. **The interceptor is working correctly.** The problem is the request never had a token attached in the first place. --- ## 2. Fix Plan ### 2.1 Primary Fix: Frontend — [`ledger.vue`](frontend_admin/pages/finance/ledger.vue) **File to modify:** `frontend_admin/pages/finance/ledger.vue` **Changes:** 1. Add `getHeaders()` helper function in the `