One login, many machines

Like many people, I have personal and work accounts for both Claude and Codex, and I want all of them available on every machine I use. Copying OAuth tokens between laptops, CI and containers gets old fast, and it means every one of those machines ends up holding a refresh token. Karan recently wrote up a neat CLIProxyAPI + OpenCode setup for exactly this problem, which got me looking at what else is out there. oh-my-pi (omp) turns out to ship its own take on it: an auth broker and an auth gateway. This post is the short version of how to set it up and point Amp at it.

How it fits together

There are two services:

  • omp auth-broker owns the credentials. It keeps them in a SQLite vault, is the only process that ever sees refresh tokens and refreshes them in the background.
  • omp auth-gateway is what clients talk to. It speaks the OpenAI Chat Completions, OpenAI Responses and Anthropic Messages APIs, fetches the right credential from the broker and forwards the request. Clients only ever hold a gateway token, never a provider token.

Both run on one always-on box (a homelab VM is perfect for this). Everything else, be it a laptop, CI or a container, just points at the gateway. Here’s the full picture of what we’ll end up with, including the separate gateway for Amp further down:

Clients reach the main gateway through a VPN with basic auth, Amp reaches its own gateway through public HTTPS, and both gateways get credentials from the broker, which is the only holder of refresh tokens.

Setting it up

Installing and logging in

On the box that will hold the credentials, we install omp and log in once per account:

1
2
3
4
5
curl -fsSL https://omp.sh/install | sh

# Repeat once per account
omp auth-broker login anthropic
omp auth-broker login openai-codex

If the box is headless, we can run the login from a laptop with --via. It tunnels the OAuth callback over SSH and stores the credential on the remote host:

1
omp auth-broker login anthropic --via=me@homelab

If you’re already using CLIProxyAPI, omp auth-broker import ~/.cli-proxy-api imports its credential files directly, so there’s no need to log in again.

Starting the broker and gateway

1
2
3
omp auth-broker serve                       # 127.0.0.1:8765
OMP_AUTH_BROKER_URL=http://127.0.0.1:8765 \
omp auth-gateway serve --bind=100.64.0.10:4000 --clients=/etc/omp/clients.json

Both generate their bearer tokens on first start (~/.omp/auth-broker.token and ~/.omp/auth-gateway.token), and the gateway picks up the broker token from that file since it’s on the same host. The broker should stay on localhost. The gateway does no TLS of its own, so I’d bind it to a Tailscale or WireGuard address (as above) or put it behind a reverse proxy.

One token per client

The gateway token can use every account, so it shouldn’t leave the box. Instead, each client gets its own token in the --clients file, pinned to specific accounts. omp usage accounts lists the account identities we need for that:

1
2
TOKEN=$(openssl rand -hex 32)
printf %s "$TOKEN" | sha256sum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"work-laptop": {
"tokenSha256": "<sha256 of the work token>",
"models": ["anthropic/*", "openai-codex/*"],
"accounts": {
"anthropic": ["email:me@work.example|org:org-work"],
"openai-codex": ["email:me@work.example|org:ws-work"]
}
},
"personal": {
"tokenSha256": "<sha256 of the personal token>",
"accounts": {
"anthropic": ["email:me@personal.example"]
}
}
}

The file only stores hashes, which I like. A client with accounts set can never borrow another account or fall back to an API key, so if its account is rate limited, the request simply fails. The gateway re-reads the file every 10 seconds, so write a temp file and mv it into place instead of editing it live.

Pointing clients at it

Any OpenAI-compatible client works with http://<gateway>:4000/v1 as the base URL and its client token as the API key. Anthropic clients use the same host for /v1/messages. A quick way to check that the token is scoped correctly:

1
curl -s -H "Authorization: Bearer $TOKEN" http://100.64.0.10:4000/v1/models | jq '.data[].id'

/v1/models also returns context length, output limits and tool support, so clients that read it get everything they need. OpenCode doesn’t read it, so there you still list the models in its config (one provider per client token, all with the same base URL).

Using it from Amp

Amp can route models through a Custom URL connection, which means its Anthropic models can run on your Claude seats instead of Amp credits. The catch is that Amp calls the connection from its own servers, so the gateway has to be reachable over public HTTPS. I wouldn’t expose the main gateway for that. Instead, we run a second gateway just for Amp, with its own token and only the seats we want Amp to use:

1
Amp → public HTTPS → omp auth-gateway (Amp only) → omp auth-broker → Anthropic

An account pool file decides what this gateway can see. "*": false hides every other provider and "apiKeys": false keeps API keys out as well. If you have more than one seat, list them all, so Amp keeps working when one hits its weekly limit:

1
2
3
4
5
6
7
{
"*": false,
"anthropic": {
"accounts": ["email:seat1@example.com|org:org-team", "email:seat2@example.com"],
"apiKeys": false
}
}

We start it under its own omp profile, so it gets a separate bearer token that can be rotated or deleted without touching the main gateway. It binds to localhost and a tunnel does the public part. Never use --no-auth here: without a proxy check in front (see the gotchas), the bearer token is the only thing guarding it.

1
2
3
4
5
6
# Read the broker token before switching profiles
export OMP_AUTH_BROKER_TOKEN=$(omp auth-broker token)
export OMP_PROFILE=amp
OMP_AUTH_BROKER_URL=http://127.0.0.1:8765 \
OMP_AUTH_BROKER_ACCOUNT_POOL_FILE=/etc/omp/amp-pool.json \
omp auth-gateway serve --bind=127.0.0.1:4100

For the public part, any mechanism that terminates HTTPS and forwards to 127.0.0.1:4100 works: a Cloudflare Tunnel, Tailscale Funnel or a TLS reverse proxy. Just make sure it only forwards to the gateway’s port and nothing else on that box.

Then we add the connection in Amp under Settings → Model Routing → Add → Custom URL, or from the CLI. Piping the token in keeps it out of the shell history:

1
2
3
4
5
6
7
8
9
ssh me@homelab 'OMP_PROFILE=amp omp auth-gateway token' | \
amp config model-providers add-router custom-url \
--name "omp gateway" \
--api-format anthropic-messages \
--base-url https://omp.example.com/v1 \
--model-mapping 'anthropic/*' \
--api-key-file - --personal --active

amp config model-providers check-access --provider-model anthropic/claude-opus-5-5

check-access runs a tiny inference and tells you which connection served it. Amp tries personal connections before workspace ones and goes top to bottom within each list, so if another connection also covers anthropic/*, move this one up with amp config model-providers set-priority <id> 0. Back on the broker, omp usage --provider anthropic shows each seat’s 5-hour and 7-day usage, which is handy for keeping an eye on the limits.

Gotchas

  • All your logins live in one place: the broker holds the refresh token for every account you sign in with. That’s convenient, but it also creates a new single target: anyone who gets onto that box gets all of your accounts at once. Treat it like a password manager. Keep the broker on localhost, run nothing else exposed on that host and keep the token files readable only by the service user.
  • Gateway access means access to your accounts: anyone holding a token for the gateway can use every login behind it (or every login its accounts allow), with the usage billed to your seats. Be careful if you want to share it with your team to pool accounts. Give each person their own client token so you can see and revoke them individually, and never hand out the main gateway token.
  • Don’t expose it raw: I’d strongly suggest putting some auth in front of the gateway instead of relying on its token alone. For anything behind a VPN, a reverse proxy with basic auth is usually enough. Clients already send the gateway token in the Authorization header, so either let the proxy check basic auth and inject the gateway token itself, or have it check a separate secret header. For the public Amp endpoint, use the header, since Amp can send it as a secret custom header on the connection.
  • Forgetting accounts: a client entry without it can use every account the gateway holds, API keys included. Treat that field as mandatory.
  • Two services: run both under systemd and run omp auth-gateway check after changes to confirm every credential still works.
  • No raw passthrough: every request goes through omp’s provider code, so a brand new upstream API feature has to land in omp before you can use it.

You can also theoretically use this for proper account pooling. Say you’re a security team that has to rotate through a pool of accounts shared across people: put them all in the broker, give everyone a client token for the pool and let the gateway move on to the next account when one hits its limit. Theoretically ofc, i’d never suggest even thinking about breaking ToS.

Summary

For a single laptop, Karan’s setup is simpler and probably all you need. Once you have several machines, CI or Amp in the mix, separating the broker from the gateway is the cleaner design: the refresh tokens live in exactly one place and every client gets a token you can revoke! The full reference is in the omp docs if you want to dig deeper.