Skip to content

Session restore never reads the auth cookie: wallet session always fails #419

Description

@YaronZaki

Labels / Complexity: bug, auth, api, FrontEnd, Backend · Extremely High — 500

Problem

The cookie-based session flow is broken end-to-end. POST /api/auth/connect in quantara/web_app/api/auth.py sets an httpOnly cookie named wallet_id containing a session token:

response.set_cookie(key="wallet_id", value=session_token, httponly=True, secure=True, samesite="strict", max_age=..., path="/")

but no code path ever reads that cookie back. get_session in the same file takes wallet_id as a query parameter and 401s when it is absent:

async def get_session(request: Request, wallet_id: str | None = None):
    # Note: Your REPO-002 auth middleware will automatically populate wallet_id from the cookie
    if not wallet_id:
        raise HTTPException(status_code=401, detail="No active wallet session")
    return {"authenticated": True, "walletId": wallet_id}

There is no REPO-002 middleware, and session_store (quantara/web_app/api/session.py, which holds session:{token} -> wallet_id in Redis) is only ever written by create_session and deleted by delete_session; nothing calls session_store.get_wallet_id(token) to authenticate a request.

The frontend assumes the cookie works. initializeSession in quantara/frontend/src/stores/useWalletStore.js calls axios.get('/api/auth/session') with no query params and expects response.data.walletId. Because the backend requires the wallet_id query param, the request always 401s, and initializeSession always falls into its catch and sets walletId: null. Consequence: a returning user is never recognised from their cookie, the wallet must be reconnected on every load, and the session token in Redis is dead weight.

Root cause

# api/auth.py
async def get_session(request: Request, wallet_id: str | None = None):
    if not wallet_id:                       # ← reads a query param, never request.cookies["wallet_id"]
        raise HTTPException(status_code=401, detail="No active wallet session")
// stores/useWalletStore.js
const response = await axios.get('/api/auth/session'); // ← no query param, no cookie is sent/read

Why this is architecturally hard

  1. The fix must decide the authentication model: cookie-as-session (read request.cookies["wallet_id"], look up session_store.get_wallet_id, and inject the wallet into the request) versus the existing header-signature flow (verify_wallet_signature in quantara/web_app/api/wallet_auth.py). Both exist today and are not reconciled.
  2. A cookie-session lookup needs a reusable FastAPI dependency or middleware so the rest of the API can actually authenticate from the cookie; this touches every endpoint's trust boundary, not just /api/auth/session.
  3. session_store is a module-level singleton in quantara/web_app/api/session.py, while wallet_auth.py uses an in-memory _nonce_store; reconciling the two session/credential stores is a prerequisite for a coherent fix.
  4. The frontend initializeSession has no timeout/error differentiation, so any fix must also decide how the UI treats "no session" vs "network error" (currently both collapse to walletId: null).

Proposed design

A minimal coherent path:

@router.get("/session")
async def get_session(request: Request):
    token = request.cookies.get("wallet_id")
    wallet_id = await session_store.get_wallet_id(token) if token else None
    if not wallet_id:
        raise HTTPException(status_code=401, detail="No active wallet session")
    return {"authenticated": True, "walletId": wallet_id}

plus a documented decision on whether the cookie or the signature header is authoritative for protected endpoints.

Downstream impact

/api/auth/session is the only endpoint whose contract changes, but if a cookie-session dependency is introduced, protected endpoints (quantara/web_app/api/position.py, vault.py, user.py) can adopt it. The frontend consumer is quantara/frontend/src/stores/useWalletStore.js.

Acceptance criteria

Service

  • GET /api/auth/session authenticates from the httpOnly wallet_id cookie via session_store and returns the stored walletId.
  • A missing, expired, or invalid cookie returns 401; a valid cookie returns the wallet id.
  • The REPO-002 comment is removed or replaced with the actual mechanism.

Tests

  • Backend tests cover valid-cookie, missing-cookie, and expired-session cases.
  • A frontend test covers initializeSession receiving and clearing walletId from the session response.
  • Tests run via cd quantara && poetry run pytest web_app/tests and cd quantara/frontend && yarn test:run.

Out of scope

Do not redesign wallet auth or migrate off the signature flow in this issue; only make the cookie session actually round-trip.

Getting started

Files in scope: quantara/web_app/api/auth.py, quantara/web_app/api/session.py, quantara/frontend/src/stores/useWalletStore.js. Verify with:

cd quantara && poetry run pytest web_app/tests -k session
cd quantara/frontend && yarn test:run

Good first files to read: quantara/web_app/api/auth.py, quantara/web_app/api/session.py, quantara/web_app/api/wallet_auth.py, quantara/frontend/src/stores/useWalletStore.js.

Metadata

Metadata

Assignees

Labels

BackendFrontEndGrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third CampaignapiImported from PRODUCTION_ISSUES.mdauthImported from PRODUCTION_ISSUES.mdbugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions