# Dictation WebSocket Authentication vs HTTP API in VoiceStudio: How WS-Ticket Exchanges Work

> Discover how VoiceStudio secures WebSocket dictation streams with ws-tickets, a more efficient alternative to session-cookie authentication for HTTP APIs. Learn about ws-ticket exchanges.

- Repository: [Palash Debnath/VoiceStudio](https://github.com/debpalash/VoiceStudio)
- Tags: internals
- Published: 2026-09-06

---

**VoiceStudio uses session-cookie authentication for HTTP endpoints but requires a short-lived, single-use ws-ticket for WebSocket dictation streams to improve security and reduce overhead.**

The `debpalash/VoiceStudio` backend implements a dual authentication system that separates long-lived session management from real-time streaming security. Standard REST endpoints rely on persistent session cookies, while the live dictation WebSocket uses an ephemeral token exchange pattern. This design prevents sensitive session credentials from being exposed on persistent socket connections while maintaining a lightweight handshake for audio streaming.

## HTTP API Authentication: Session-Cookie Based

The VoiceStudio HTTP API follows a traditional session-based authentication flow common in FastAPI applications.

### How HTTP Authentication Works

1. **Login endpoint** (`POST /api/auth/login`) validates user credentials and returns a **signed session cookie**
2. **Subsequent requests** include this cookie in the `Cookie` header
3. **Server validates** the cookie signature on every request before processing

This pattern works well for stateless request-response cycles where each call is independent. The session cookie persists across the user's browsing session, allowing convenient access to protected endpoints like user settings, transcription history, and file management.

### Key HTTP Authentication Files

- **[`backend/api/routers/auth.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/routers/auth.py)** — Contains login/logout endpoints and session middleware configuration
- Session cookies are configured with standard HTTPOnly and Secure flags in production

## WebSocket Authentication: The WS-Ticket Pattern

Real-time dictation introduces unique security challenges. Maintaining a long-lived session cookie on a persistent WebSocket connection increases the attack surface if the connection is compromised. VoiceStudio solves this with a **ws-ticket exchange mechanism**.

### Step 1: Request a WS-Ticket via HTTP

The client first calls a dedicated HTTP endpoint to exchange their valid session for a temporary ticket.

```python
import requests

# Existing session from prior HTTP authentication

session_cookie = {"session": "eyJ1c2VyX2lkIjogMTIzLCAiZXhwIjogMTY5ODU0NDYzMn0..."}

# Exchange session for ws-ticket

response = requests.post(
    "https://api.voicestudio.example/api/auth/ws-ticket",
    cookies=session_cookie
)

# Extract the single-use ticket

ws_ticket = response.json()["ws_ticket"]

# Example: "ws_tkt_a3f8b2d9e7c1f5a4b8e2d9c7f1a5b3e9"

```

The **`POST /api/auth/ws-ticket`** endpoint in [`backend/api/routers/auth.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/routers/auth.py) performs these operations:

- Validates the incoming session cookie
- Generates a cryptographically random ticket string
- Stores the ticket in an **in-memory cache** with user ID mapping
- Sets an **expiration timer** (typically 30-60 seconds)
- Returns the ticket to the client

### Step 2: Establish WebSocket with Ticket

The client includes the ws-ticket when opening the WebSocket connection. VoiceStudio supports this via query parameter:

```python
import asyncio
import websockets

async def connect_dictation_stream():
    ws_url = (
        f"wss://api.voicestudio.example/ws/dictation"
        f"?ws-ticket={ws_ticket}"
    )
    
    async with websockets.connect(ws_url) as websocket:
        # Ticket is validated during handshake

        # Connection upgrades to authenticated dictation channel

        
        # Stream audio chunks

        while audio_chunk := microphone.read():
            await websocket.send(audio_chunk)
            
            # Receive transcription results

            result = await websocket.recv()
            print(f"Transcription: {result}")

asyncio.run(connect_dictation_stream())

```

### Step 3: Server-Side Ticket Validation

The WebSocket handler in **[`backend/api/routers/speech_platform.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/routers/speech_platform.py)** (or equivalent dictation router) executes:

1. **Extracts** `ws-ticket` from query parameters
2. **Validates** against the in-memory ticket store
3. **Checks** expiration timestamp
4. **Verifies** single-use status (immediately marks as consumed)
5. **Resolves** user identity from ticket mapping
6. **Upgrades** connection or rejects with `401 Unauthorized`

```python

# Conceptual implementation based on VoiceStudio pattern

async def ws_dictation_endpoint(websocket: WebSocket, ws_ticket: str):
    # Ticket validation

    ticket_data = await ticket_store.consume(ws_ticket)
    if not ticket_data or ticket_data["exp"] < time.time():
        await websocket.close(code=1008, reason="Invalid or expired ticket")
        return
    
    user_id = ticket_data["user_id"]
    
    # Upgrade to authenticated session

    await websocket.accept()
    
    # Begin dictation streaming...

```

## Key Differences: HTTP vs WebSocket Authentication

| Aspect | HTTP API | WebSocket Dictation |
|--------|----------|---------------------|
| **Credential type** | Persistent session cookie | Ephemeral ws-ticket |
| **Lifetime** | Hours to days | 30-60 seconds |
| **Usage count** | Unlimited per session | **Single-use only** |
| **Transmission** | Cookie header on every request | Query parameter once at handshake |
| **Validation location** | Every HTTP request middleware | WebSocket accept handler |
| **Revocation** | Session logout invalidates cookie | Automatic expiration or single-use consumption |

## Security Benefits of the WS-Ticket Pattern

The VoiceStudio implementation provides several security advantages:

- **No long-lived credentials on socket** — If the WebSocket connection is intercepted, the attacker gains no persistent session access
- **Replay attack prevention** — Single-use consumption means stolen tickets cannot be reused
- **Time-bound exposure** — Short expiration windows limit the attack window
- **Isolated privilege scope** — The ticket grants only dictation access, not full API capabilities

## Handling Ticket Exchange Failures

Production implementations should handle these edge cases:

```python
import websockets.exceptions

async def robust_dictation_connect():
    try:
        ws = await websockets.connect(ws_url)
        
        # Check for immediate close (invalid ticket)

        try:
            await asyncio.wait_for(ws.ping(), timeout=5.0)
        except asyncio.TimeoutError:
            # Server rejected ticket, request new one

            ws_ticket = await refresh_ws_ticket()
            # Retry with fresh ticket...

            
    except websockets.exceptions.InvalidStatusCode as e:
        if e.status_code == 401:
            # Ticket expired or invalid

            raise AuthenticationError("WS ticket rejected")

```

## Summary

- **HTTP authentication** in VoiceStudio uses traditional session cookies managed through [`backend/api/routers/auth.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/routers/auth.py)
- **WebSocket authentication** requires a **ws-ticket exchange**: obtain via `POST /api/auth/ws-ticket`, consume once at WebSocket handshake
- **Tickets are single-use and short-lived** (30-60 seconds), stored in-memory and immediately consumed upon validation
- **[`backend/api/routers/speech_platform.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/routers/speech_platform.py)** handles the WebSocket path and ticket verification logic
- This dual-mode design **separates concerns**: persistent sessions for API calls, ephemeral tokens for real-time streaming

## Frequently Asked Questions

### How long does a ws-ticket remain valid?

VoiceStudio ws-tickets typically expire within **30 to 60 seconds** of issuance. This short window balances security with network latency—enough time for the client to complete the WebSocket handshake, but insufficient for meaningful attacker exploitation if intercepted.

### Can I reuse a ws-ticket if my WebSocket connection drops?

**No.** Tickets are **single-use** by design. If your connection fails, you must call `POST /api/auth/ws-ticket` again to obtain a fresh ticket. The server marks each ticket as consumed immediately upon successful WebSocket acceptance, preventing replay attacks.

### Does the WebSocket connection require the session cookie after ticket validation?

**No.** Once the ws-ticket is validated during the WebSocket handshake, the server associates the connection with the authenticated user internally. The session cookie is not required for subsequent messages on that socket, which is precisely why the ticket pattern improves security for long-lived connections.

### What happens if I send an invalid or expired ws-ticket?

The server responds with a **WebSocket close frame** using code `1008` (policy violation) and reason `"Invalid or expired ticket"`. The HTTP upgrade fails, and the client receives no further communication on that connection. You should implement retry logic that requests a fresh ticket and reattempts the connection.