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

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, the @router.post("/ws-ticket") handler implements this logic:

@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 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:

Practical Code Examples

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

Requesting a Ticket (Client)

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)

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:

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.
  • 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 and integrated into the speech platform via 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. Clients must establish their connection within this window or request a new 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. This value is typically loaded from environment variables or a secrets manager specified in the VoiceStudio configuration files.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →