AUTHENTICATION

Auth without a public API

How uivoid authenticates outbound calls to your real API, and what to do when that API was never meant to face the internet.

Open in Claude

Two directions of auth

uivoid never duplicates your data or logic — it proxies tool calls through to your real endpoints. That means every deployment needs both an inbound credential (agent → uivoid) and an outbound credential (uivoid → your real API). This page covers the outbound side.

Case 1 — You already have an auth system

uivoid isn't a broker for your login. It never shows its own login screen and never intercepts your users' credentials — you tell it how to verify tokens your own auth system issues, and it does exactly that, then forwards a real, per-user credential to your API. Requests arrive as that specific user, not a shared uivoid identity.

A project picks exactly one login_mode:

oauth — you already run a real OAuth/OIDC provider

Auth0, Okta, Clerk, WorkOS, or your own. You give uivoid:

issuer                 # your provider
jwks_url                # verifies token signatures
audience
target_authorize_url
target_token_url
target_client_id        # uivoid registers as a confidential client of YOUR provider
target_client_secret
target_scope             # optional, e.g. "offline_access" for a refresh token

This target_client_id/target_client_secret pair is a separate, internal-to-uivoid OAuth leg against your provider — decoupled from whatever OAuth dance the calling AI agent itself does with uivoid.

The first time a user connects an AI agent to your uivoid-powered MCP server, uivoid redirects them to yourprovider's real login page (never uivoid's). They log in exactly as they always do. uivoid receives the resulting token via a server-to-server exchange, validates it against your issuer/jwks_url/audience, and stores an encrypted reference so it keeps working without asking the user to log in again — until your provider's refresh token itself expires or is revoked. (Without target_scope requesting something like offline_access, most providers won't issue a refresh token at all.)

custom_handoff — you have your own login, no separate OAuth server

Just your own system issuing your own JWTs (RS256 or ES256 signed). You give uivoid:

issuer
jwks_url
audience
handoff_url    # a page on your own site

uivoid redirects the user's browser to:

{your handoff_url}?callback=<uivoid_url>&state=<opaque>

Your page authenticates them however you already do (or confirms they're already logged in), then redirects back to that callback URL — carrying the token in a URL fragment, never a query string:

<callback>#token=<jwt>&refresh_token=<optional>

Fragments never reach server logs or Referer headers, which matters for a token in transit. uivoid still validates the delivered token against your configured issuer/jwks_url/audiencebefore trusting anything — landing on that callback URL isn't itself proof of identity, the JWT signature has to check out.

Both modes:after that one-time login, every subsequent AI-agent tool call carries that specific user's real credential to your API. uivoid never flattens users into one shared identity, and never asks for a login again unless your own system revokes access.

Configuring it today: there's no CLI flag yet (unlike Case 2's --auth-key/--auth-header). Set it from the project's Settings tab in the uivoid dashboard, or directly:

PATCH /api/projects/<id>/passthrough-config

Send the fields above as JSON. target_client_secretis sent as plaintext once and stored encrypted — it's never returned. GET the same URL for the current config: it reports has_target_client_secret: true/false in place of the secret, and 404s if nothing's configured yet.

Case 2 — No existing auth (small/internal APIs)

If there's nothing to federate against, there are two ways to get uivoid a credential for that API:

  • Bring your own key. If the API already checks for a specific static secret, hand it to uivoid at creation time (uivoid create ... --auth-key <value>) or afterward (uivoid credentials <project> --auth-key <value>). uivoid replays that exact value on every outbound call — your API doesn't have to change at all.
  • Let uivoid generate one. Run create with no --auth-key/--auth-header, and uivoid issues its own secret, prints it once right after creation, and you configure your API to accept it — see the CLI reference.

Either way the secret is encrypted at rest (not just hashed — uivoid has to be able to reproduce it on every outbound call), scoped to read / write / destructive, and every call is logged to AuditLog.

By default it's attached as a bearer token; pass a custom header name (--auth-header "x-api-key:<value>") if your API expects the secret somewhere other than Authorization:

POST https://internal-api.yourcompany.com/v1/orders/42/refund
Authorization: Bearer <outbound_api_key>

Your endpoint verifies that token before doing anything. This is what "not publicly exposed" usually means in practice: the route can be internet-facing, but it's useless to anyone without that secret.

Case 3 — Your API isn't reachable from the internet at all

An outbound key only solves authentication. If your API sits behind a VPN, a private subnet, or has no public IP, uivoid's middleware has nothing to connect to — that's a network reachability problem, not an auth problem.

Option A — IP allowlist

Works when uivoid runs from a small, stable set of egress IPs (true for the current single-server setup). Open one firewall rule for uivoid's server IP and keep everything else closed.

# your firewall / security group
ALLOW  203.0.113.42/32  →  443   # uivoid's egress IP
DENY   0.0.0.0/0        →  443

Option B — Outbound-only tunnel / agent

A lightweight agent runs inside your network and dials outto uivoid — no inbound firewall rule needed on your side at all (Tailscale/ngrok-style reverse tunnel). uivoid sends the tool call over that already-open connection instead of opening a new one to you. Best when your API has no public IP at all and you don't want to punch any inbound hole for it, even a scoped one.

Choosing between them

SituationUse
Your own auth system already issues tokens, care about per-user credentialsCase 1 — oauth or custom_handoff passthrough
Small/internal API, no existing authCase 2 — platform-issued outbound key
API is public-reachable but shouldn't be discoverableCase 2 — the key does the gating
API has no public IP / fully private networkCase 3 — IP allowlist or outbound tunnel

These compose: an org can be on Case 1 or 2 for auth and still need Case 3 for reachabilityif their real backend isn't internet-facing at all.