Skip to content

security: replace JWT query parameter with one-time ticket for WebSocket auth #343

@Aureliolo

Description

@Aureliolo

Problem

The WebSocket connection currently passes the JWT as a URL query parameter:

const url = `${getWsUrl()}?token=${encodeURIComponent(token)}`

This exposes the bearer token in:

  • nginx/proxy access logs (full URL including query string)
  • Browser history
  • Referer headers on any redirects
  • Server-side access logs

Flagged by 3 independent reviewers on PR #342 (Greptile, Gemini, CodeRabbit).

Solution

Implement a short-lived, single-use WS ticket exchange:

  1. Backend: Add POST /api/v1/auth/ws-ticket endpoint that accepts a valid JWT and returns a single-use ticket (random token, ~30s TTL, stored in-memory or Redis)
  2. Frontend: Before opening WebSocket, call the ticket endpoint, then connect with ?ticket=<ticket> instead of ?token=<jwt>
  3. Backend WS handler: Validate and consume the ticket on connect (single-use = delete after first use)

This ensures:

  • No long-lived credentials in URLs/logs
  • Ticket is useless if intercepted (single-use + short TTL)
  • JWT remains in Authorization header for REST calls only

Scope

  • Backend: new endpoint + ticket store + WS handler changes
  • Frontend: web/src/stores/websocket.ts — replace direct JWT URL with ticket exchange in connect()
  • The TODO comment at websocket.ts:50-51 documents this debt

Acceptance Criteria

  • JWT never appears in WebSocket URL
  • Ticket expires after 30 seconds or first use
  • Ticket exchange requires valid JWT
  • WebSocket connection fails gracefully if ticket exchange fails
  • Existing WS reconnect logic works with ticket refresh

Metadata

Metadata

Assignees

No one assigned

    Labels

    prio:highImportant, should be prioritizedspec:securityDESIGN_SPEC Section 12 - Security & Approval Systemtype:featureNew feature implementation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions