# How Remote Access Is Authenticated in VoiceStudio: JWT Ticket Flow Explained

> Discover how VoiceStudio authenticates remote access using JWT tickets. Learn about the WebSocket ticket flow and secure client connections to the /api/auth/ws-ticket endpoint.

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

---

**VoiceStudio authenticates remote access by issuing short-lived JSON Web Token (JWT) tickets via the `/api/auth/ws-ticket` endpoint, which clients must present as a `ws_ticket` query parameter to establish authorized WebSocket connections.**

VoiceStudio (debpalash/VoiceStudio) secures remote API interactions and real-time streams through a token-based authentication architecture. The system generates signed, ephemeral credentials that bind client identities to persistent authorization records, ensuring every remote request is verified against revocable session authorities according to the source code.

## WebSocket Ticket Authentication Flow

Remote access in VoiceStudio relies on a two-step ticket mechanism that separates initial authentication from connection establishment.

### Ticket Generation at `/api/auth/ws-ticket`

When a client requires access to protected WebSocket streams or RPC endpoints, it first POSTs to the `/api/auth/ws-ticket` route. The server validates the existing session or API key, then mints a JWT containing the user identity (`sub` claim), allowed scopes, and a short expiration time (typically 300 seconds).

In [`backend/api/routers/auth.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/routers/auth.py), the `@router.post("/ws-ticket")` handler implements this logic:

```python
@router.post("/ws-ticket")
async def create_ws_ticket(payload: TicketRequest, request: Request):
    user = await authenticate_user(request)          # verify session / API key

    token = jwt.encode(
        {"sub": user.id, "exp": time.time() + 300},
        settings.SECRET_KEY,
        algorithm="HS256",
    )
    return {"ws_ticket": token}

```

The resulting `ws_ticket` is a signed string that serves as a temporary capability token.

### Ticket Presentation and Validation

To open a WebSocket, the client includes the ticket as a `ws_ticket` query parameter or transmits it as a cookie during the handshake. The server validates the JWT signature against `settings.SECRET_KEY` and checks the expiration claim before authorizing the connection.

The [`speech_platform.py`](https://github.com/debpalash/VoiceStudio/blob/main/speech_platform.py) router references this flow through the `websocket_ticket_endpoint` constant, which points clients to the ticket generation route. Once validated, the server binds the connection to a persistent **authority record** stored in the session, which is checked on every subsequent RPC call to ensure the caller retains valid permissions.

## Implementation Files in VoiceStudio

The authentication flow is implemented across dedicated router modules and verified by integration tests:

- [`backend/api/routers/auth.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/routers/auth.py): Contains the POST handler that mints signed JWTs and defines the ticket payload schemas.
- [`backend/api/routers/speech_platform.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/routers/speech_platform.py): Exposes the WebSocket endpoint constant (`websocket_ticket_endpoint`) and enforces ticket validation on incoming connections.
- [`tests/test_auth_session_api.py`](https://github.com/debpalash/VoiceStudio/blob/main/tests/test_auth_session_api.py): Validates the end-to-end flow, ensuring that requests lacking a valid `ws_ticket` are rejected and that properly signed tickets grant access.

## Practical Code Examples

Below are runnable snippets demonstrating the client flow and server configuration.

### Requesting a Ticket (Client)

```python
import requests

resp = requests.post(
    "https://voice.studio/api/auth/ws-ticket",
    json={"scopes": ["ws"]},
    cookies={"session": "your_session_cookie"}
)
ticket = resp.json()["ws_ticket"]

```

### Establishing an Authenticated WebSocket (Client)

```python
import websockets
import asyncio

async def connect_stream():
    ws_url = f"wss://voice.studio/api/ws?ws_ticket={ticket}"
    async with websockets.connect(ws_url) as ws:
        await ws.send("hello")
        response = await ws.recv()
        print(response)

asyncio.run(connect_stream())

```

### Server-Side Ticket Verification

The server extracts the `ws_ticket` from the incoming request, decodes it using the shared secret, and loads the corresponding user authority:

```python
from jose import jwt

def verify_ticket(token: str):
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
        return payload["sub"]  # user_id bound to authority

    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Ticket expired")

```

## Session Lifecycle and Revocation

Once a ticket is consumed, the associated **authority record** governs the session lifecycle. VoiceStudio supports explicit revocation—when a user logs out or an API key rotates, the authority is invalidated in the store. Subsequent requests using the same ticket signature fail validation, forcing the client to request a fresh ticket from `/api/auth/ws-ticket`. This design ensures that compromised or expired credentials cannot sustain long-lived unauthorized access.

## Summary

- VoiceStudio uses a JWT-based ticket system for **remote access authentication**.
- Clients obtain short-lived `ws_ticket` tokens by POSTing to `/api/auth/ws-ticket` implemented in [`backend/api/routers/auth.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/routers/auth.py).
- Tickets are presented as query parameters or cookies during WebSocket handshakes, validated against `settings.SECRET_KEY` using the HS256 algorithm.
- Valid tickets bind the connection to a persistent authority record checked on every RPC call.
- The flow is tested in [`tests/test_auth_session_api.py`](https://github.com/debpalash/VoiceStudio/blob/main/tests/test_auth_session_api.py) and integrated into the speech platform via [`backend/api/routers/speech_platform.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/routers/speech_platform.py).

## Frequently Asked Questions

### How long is a VoiceStudio WebSocket ticket valid?

Tickets are configured with a 300-second (5-minute) expiration by default, as set by the `exp` claim in [`backend/api/routers/auth.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/routers/auth.py). Clients must establish their connection within this window or request a new ticket.

### Can I use a session cookie instead of a query parameter for the ticket?

While the primary mechanism uses the `ws_ticket` query parameter, the authentication middleware also checks for the ticket value in cookies. However, the query parameter approach is recommended for WebSocket compatibility across different client libraries.

### What happens if a ticket is stolen or leaked?

Because tickets are short-lived and bound to specific user authorities, the exposure window is limited. Additionally, server-side revocation of the underlying authority record immediately invalidates active sessions derived from that ticket, forcing re-authentication via the `/api/auth/ws-ticket` endpoint.

### Where is the secret key for signing tickets configured?

The JWT signing key is referenced as `settings.SECRET_KEY` in [`backend/api/routers/auth.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/routers/auth.py). This value is typically loaded from environment variables or a secrets manager specified in the VoiceStudio configuration files.