omg/docs

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:

TokenHow it is mintedAudienceWhere it is accepted
User/app JWTPOST /token with an existing omg session and { appId }The requested app id; the dashboard/CLI platform token uses vibesControl-plane and owner-scoped sandbox routes
Service JWTOn-box apps/auth/scripts/mint.tsvibes-infraPrivileged 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 — sets revokedAt. (Run on-box via bun 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

  1. bun apps/auth/scripts/mint.ts svc:worker 31536000 (1y) on the auth box. Capture the new JWT.
  2. Update the consumer:
    • svc:convexbash apps/web/scripts/convex-prod.sh -- npx convex env set VIBES_INFRA_SERVICE_TOKEN <jwt>
    • svc:workerwrangler secret put VIBES_INFRA_SERVICE_TOKEN
    • svc:cigh secret set VIBES_API_KEY --env e2e
  3. Verify the new key works (one round-trip through the consumer).
  4. 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.