# VoiceStudio Loopback-Only API Authentication Model: Share PIN, Bearer Key, and Admin-Gate Separation Explained

> Explore VoiceStudio's loopback API authentication: Share PIN, Bearer Key, admin gate separation, and local process access. Understand secure remote access methods.

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

---

**VoiceStudio's backend defaults to a loopback-only API where local processes require no credentials, with optional Share PIN, Bearer Key, and trusted-network gates for remote access, plus strict admin-route isolation that never permits PIN-based authorization.**

VoiceStudio's authentication architecture prioritizes zero-friction local development while providing layered security for network deployments. The `debpalash/VoiceStudio` repository implements this through three distinct protection mechanisms and a critical administrative boundary that prevents privilege escalation.

## How VoiceStudio's Loopback-Only Foundation Works

By default, any process on the same machine can access `http://localhost:3900` without authentication. Connections from `127.0.0.1`, `::1`, or `localhost` bypass all gates unconditionally in both `NetworkAccessMiddleware` and `BearerKeyMiddleware` as implemented in [`backend/main.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/main.py)【/cache/repos/github.com/debpalash/VoiceStudio/main/backend/main.py#L65-L106】【/cache/repos/github.com/debpalash/VoiceStudio/main/backend/main.py#L71-L118】.

This design ensures desktop integrations, local scripts, and the first-party UI operate without configuration friction while the API surface remains protected from remote access until deliberately exposed.

## Share PIN Gate: Session-Based HTTP Protection

The **Share PIN** gate controls access to non-loopback HTTP routes and is activated via the in-app *Network share* toggle. When enabled, `NetworkAccessMiddleware` intercepts every incoming HTTP request from external clients.

### PIN Presentation Methods

VoiceStudio accepts the 6-digit PIN through three channels:

- **Header**: `X-OmniVoice-Pin: <pin>`
- **Query parameter**: `?pin=<pin>`
- **Cookie**: `ov_pin=<pin>` (set automatically after first valid submission)

The middleware in [`backend/main.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/main.py) extracts and validates against the generated PIN, returning **401 "PIN required"** for mismatches. Valid PINs are echoed back as short-lived cookies to streamline subsequent requests.

### PIN Gate Limitations

The Share PIN **only protects HTTP routes**. WebSocket connections remain unauthenticated unless the Bearer Key gate is active. Static SPA assets are also exempted to allow the UI shell to load before authentication.

## Bearer Key Gate: Durable HTTP and WebSocket Protection

The **Bearer Key** gate provides stronger, persistent authentication activated by setting `OMNIVOICE_API_KEY` as an environment variable. `BearerKeyMiddleware` enforces this on all non-loopback HTTP and WebSocket traffic.

### Key Presentation Methods

Per [`backend/main.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/main.py)【/cache/repos/github.com/debpalash/VoiceStudio/main/backend/main.py#L71-L118】:

- **Preferred**: `Authorization: Bearer <key>` header
- **Legacy cookie**: `ov_key=<key>`
- **Legacy query**: `?api_key=<key>`

Invalid or missing keys trigger **401 "API key required"** for HTTP or WebSocket handshake termination with code **1008** (policy violation).

### Bearer Key Use Cases

This gate suits:
- Long-lived service integrations
- WebSocket-based real-time transcription
- Automated CI/CD pipelines
- SDK and API clients requiring stable credentials

## Trusted-Network Exemption: LAN and Tailscale Deployment

The `OMNIVOICE_TRUSTED_NETWORKS` environment variable accepts a CIDR list (e.g., `192.168.1.0/24,100.64.0.0/10`) parsed in [`backend/api/dependencies.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/dependencies.py)【/cache/repos/github.com/debpalash/VoiceStudio/main/backend/api/dependencies.py#L45-L59】. When a client's IP falls within any listed range, `is_local_host()` returns true, exempting **consumption routes** from both Share PIN and Bearer Key requirements.

This exemption **does not extend** to administrative routes regardless of network membership.

## Admin-Gate Separation: The Critical Security Boundary

VoiceStudio implements strict privilege isolation through `require_admin` in [`backend/api/dependencies.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/dependencies.py). The admin surface (`/system/*`, `/api/settings/*`) operates under fundamentally different rules:

### Default Desktop Build Behavior

Admin routes are **true-loopback only**. Even with valid Share PIN or Bearer Key presented from a remote machine, administrative endpoints return 403. This prevents brute-forceable 6-digit PINs from compromising engine installation or system configuration endpoints.

### Server Mode Relaxation

When `OMNIVOICE_SERVER_MODE=1` (Docker deployments), the loopback check relaxes because NAT can mask source IPs. However, admin access still **requires** either:
- Valid Bearer Key in `Authorization` header
- Short-lived admin session cookie from `/api/auth/session`

The Share PIN **never** authorizes admin actions. Trusted-network membership is ignored for admin routes.

## Practical Authentication Examples

### Calling HTTP Endpoints with Share PIN

```python
import requests

host = "192.168.1.42:3900"
pin = "123456"  # 6-digit PIN from Network share UI

resp = requests.get(
    f"http://{host}/v1/audio/voices",
    headers={"X-OmniVoice-Pin": pin},
)
print(resp.json())

```

### HTTP Bearer Key Authentication (Recommended)

```python
import os
import requests

api_key = os.getenv("OMNIVOICE_API_KEY")

resp = requests.post(
    f"https://gpu-box:3900/v1/audio/speech",
    headers={"Authorization": f"Bearer {api_key}"},
    json={"model": "tts-1", "voice": "alloy", "input": "Hello world"},
)

with open("speech.wav", "wb") as f:
    f.write(resp.content)

```

### WebSocket Connection with API Key

```python
import websockets
import asyncio
import os

api_key = os.getenv("OMNIVOICE_API_KEY")
ws_url = f"ws://gpu-box:3900/ws/transcribe?api_key={api_key}"

async def listen():
    async with websockets.connect(ws_url) as ws:
        await ws.send(b"... audio frames ...")
        async for msg in ws:
            print(msg)

asyncio.run(listen())

```

### Admin Session Bootstrap

```python
import requests

# Exchange master key for short-lived admin session

resp = requests.post(
    "http://localhost:3900/api/auth/session",
    headers={"Authorization": f"Bearer {api_key}"},
    json={"transport": "cookie"},
)

session_cookie = resp.cookies.get("ov_session")

# Subsequent admin calls use this HttpOnly cookie automatically

```

## Gate Combinations and Interaction

| Configuration | Result |
|-------------|--------|
| No env vars, no UI toggle | Loopback-only, no credentials required |
| Share PIN only | Remote HTTP needs PIN; WebSocket open; admin loopback-only |
| Bearer Key only | Remote HTTP and WebSocket need key; admin needs key or loopback |
| Both gates active | HTTP must pass PIN **or** key checks; WebSocket needs key |
| Trusted networks defined | Listed CIDRs skip PIN/key for consumption routes |

Both gates remain **independent**—enabling both does not create layered requirements on the same route. Each middleware checks only the credential types it recognizes.

## Summary

- **Loopback traffic** (`127.0.0.1`, `::1`, `localhost`) bypasses all authentication unconditionally
- **Share PIN** protects HTTP consumption routes with temporary, UI-generated 6-digit codes
- **Bearer Key** (`OMNIVOICE_API_KEY`) secures HTTP and WebSocket with durable credentials
- **Trusted networks** (`OMNIVOICE_TRUSTED_NETWORKS`) exempt consumption routes from PIN/key requirements for LAN/Tailscale deployments
- **Admin routes** (`/system/*`, `/api/settings/*`) require master API key or admin session; PIN authorization is explicitly rejected; trusted-network membership is ignored

## Frequently Asked Questions

### Can I use the Share PIN for WebSocket authentication?

**No.** The Share PIN gate implemented in `NetworkAccessMiddleware` only inspects HTTP requests. WebSocket connections remain unauthenticated unless you activate the Bearer Key gate with `OMNIVOICE_API_KEY`. For WebSocket protection, present the key via `?api_key=` query parameter or exchange it for a session first.

### Why does my valid PIN return 403 on `/system` or `/api/settings` endpoints?

**Administrative routes reject PIN authentication by design.** The `require_admin` dependency in [`backend/api/dependencies.py`](https://github.com/debpalash/VoiceStudio/blob/main/backend/api/dependencies.py) enforces that admin operations require either loopback origin (desktop default) or Bearer Key/server-mode session. This prevents brute-force attacks against the 6-digit PIN from compromising engine installation or system configuration capabilities.

### How do I enable authentication for a Tailscale deployment without sharing the master API key?

**Set `OMNIVOICE_TRUSTED_NETWORKS` to your Tailscale CIDR** (typically `100.64.0.0/10`). This exempts consumption routes from PIN/key requirements while keeping admin routes protected. For stricter control, enable the Share PIN in the UI—remote users on the Tailscale network will then need the temporary PIN without you distributing long-lived credentials.