Auth
User JWTs, service JWTs, JWKS rotation, and the on-behalf-of pattern.
vibes-auth (apps/auth/) is the only place that mints JWTs. Everything
else verifies via auth.omg.dev/.well-known/jwks.json.
Token families
vibes-auth signs two ES256 token families with the same issuer and JWKS:
| Token | How it is minted | Audience | Where it is accepted |
|---|---|---|---|
| User/app JWT | POST /token with an existing omg session and { appId } | The requested app id; the dashboard/CLI platform token uses vibes | Control-plane and owner-scoped sandbox routes |
| Service JWT | On-box apps/auth/scripts/mint.ts | vibes-infra | Privileged infra routes |
User tokens carry the logged-in human's sub, email, and name and default
to a five-minute lifetime. POST /token is public in the network sense but
requires a valid session cookie.
Service tokens have a sub beginning with svc:, a jti for revocation, and
normally a one-year lifetime. There is no public service-token mint
endpoint — minting requires access to the auth box.
The audience boundary is deliberate. /v1/sandboxes* additionally accepts
the vibes user audience (and owner-scoped omg_sk_ keys), while
/v1/deploys, /v1/models, and /v1/messages remain behind the default
vibes-infra verifier. The local deploy path therefore enters through
control-plane; see Deploy from a local machine.
On-behalf-of
A service principal can act for a user by adding X-On-Behalf-Of: <userId>
to its request. Handlers read identity from the resolved Principal struct
(see apps/infra/internal/auth/middleware.go), which carries either:
Principal{ Kind: "user", UserID: "abc" }
Principal{ Kind: "service", Service: "svc:convex", OnBehalfOf: "abc" }Every handler treats both shapes identically for authorization purposes; audit logs distinguish them.
Revocation
Each minted token has a jti. Active tokens live in the service_token
sqld row (apps/auth/src/schema.ts). Revocation is two endpoints:
POST /admin/service-token/revoke— setsrevokedAt. (Run on-box viabun scripts/revoke.ts.)GET /tokens/revoked— returns{ jtis: string[] }. Public, cached 10s at the edge.
Every infra binary polls /tokens/revoked every 30s into a
map[string]struct{} and rejects matching jti on the hot path. So a
revoke takes effect everywhere within ~30s plus edge-cache TTL.
Rotating a service token
bun apps/auth/scripts/mint.ts svc:worker 31536000(1y) on the auth box. Capture the new JWT.- Update the consumer:
svc:convex→bash apps/web/scripts/convex-prod.sh -- npx convex env set VIBES_INFRA_SERVICE_TOKEN <jwt>svc:worker→wrangler secret put VIBES_INFRA_SERVICE_TOKENsvc:ci→gh secret set VIBES_API_KEY --env e2e
- Verify the new key works (one round-trip through the consumer).
bun apps/auth/scripts/revoke.ts <old-jti>to cut off the previous token.
bun apps/auth/scripts/revoke.ts list shows everything live + warns on
tokens within 30 days of expiry.