Skip to content

Auth Server Architecture

How the OpenQuok CLI auth server works — request flow, endpoints, and Postgres state.

6 min read

Connect your agent today

Draft from chat, review in your calendar, and publish only what you approve.

Start for $0

Overview

The CLI auth server (agent/server) is a Node.js service that implements OAuth2 device flow. It exists so the CLI can authenticate without embedding OAuth client secrets.

Environment variables and templates live under Configuration - Agent. Implementation reference: agent/server/app.ts.

Browser routes on the web app: web/src/routes/(public)/cli/device.

OpenQuok hosted (Production)

Request flow (device login)

Components and responsibilities

  • CLI

    • Calls /device/code on the auth server API origin.
    • Opens verification_uri in a browser (production: www.openquok.com/cli/device/verify).
    • Polls /device/token until the user finishes approval.
  • Auth server (agent/server)

    • Holds the OAuth client secret and performs the server-to-server exchange with the OpenQuok API.
    • Stores short-lived device flow state in Postgres (not in memory).
    • Returns an API token to the CLI once approval is complete.
    • Emits browser URLs from BROWSER_ORIGIN when configured.
  • Web app (web, when BROWSER_ORIGINSERVER_URL)

    • Serves /cli/device/verify and proxies to the auth server using CLI_AUTH_SERVER_URL.
    • Proxies /cli/device/callback so OAuth redirects stay on the trusted web origin.
  • OpenQuok (web + API)

    • Hosts the approval UI at OPENQUOK_FRONTEND_URL + OPENQUOK_AUTHORIZE_PATH.
    • Exchanges the OAuth authorization code for an API access token via /api/v1/oauth/token.

OAuth client credentials vs user access tokens

Deployers configure one OAuth application on the auth server (OPENQUOK_OAUTH_CLIENT_ID and OPENQUOK_OAUTH_CLIENT_SECRET). That pair identifies the platform CLI app to the API. It is not the credential end users send on programmatic routes.

CredentialWho holds itPurpose
oqs_… client secretAuth server onlyServer-to-server POST /api/v1/oauth/token after the user approves in the browser
opo_… access tokenEach CLI user (stored locally after login)Authorization: Bearer on /api/v1/public/*

Every successful device login produces a new opo_… token. Tokens are not shared across users or workspaces.

Workspace scoping and subscription plans

Plan limits are not inherited from whoever operates cli-auth.openquok.com. They attach to the workspace the user selects when authorizing the app — not to the auth server. The steps below bind that workspace from device verification through each later public API request.

Device verification and OAuth UI

User completes device verify → OAuth authorize UI.

Workspace selection

User picks a workspace and chooses Authorize.

Issue programmatic token

API upserts oauth_authorizations (user, workspace, app) and issues opo_….

Enforce plan limits on each request

Each /public/* request: resolve token → organization_id → subscription guard for that workspace.

The auth server stores organization_id on the completed device_requests row (returned from token exchange) so the CLI knows which workspace the token belongs to.

Workspace isolation

Users do not share one workspace.

Device login does not hand every CLI user the same token or the operator’s workspace.

QuestionAnswer
Do all CLI users share one opo_… token?No. Each login mints a new token tied to that user’s authorization row.
Can User B’s token read User A’s workspace?No. The API resolves workspace only from the token’s authorization; another user’s channels and posts are unreachable.
Can a Solo user get a token for the operator’s workspace without permission?No. Approve requires an active user_organizations membership for the selected workspace.
Can someone use the client secret to call /public/*No. The secret only exchanges OAuth codes; it is not accepted as a programmatic bearer token.

Endpoints

Auth server API (SERVER_URL)

MethodPathDescription
POST/device/codeCLI starts the flow. Returns device_code, user_code, verification_uri.
POST/device/tokenCLI polls with {"device_code": "..."} until completed.
GET/healthHealth check.

Browser steps

When BROWSER_ORIGIN equals SERVER_URL (typical local dev), the auth server serves these directly:

MethodPathDescription
GET/device/verifyCode entry page. Supports ?code= prefill.
POST/device/verifyValidates code; redirects to OAuth authorize UI.
GET/device/callbackOAuth redirect target; exchanges code and stores token.

When BROWSER_ORIGIN is the web app (OpenQuok production), the same behavior is exposed at:

MethodPathHost
GET/cli/device/verifyBROWSER_ORIGIN
POST/cli/device/verifyproxied to auth server
GET/cli/device/callbackproxied to auth server

Postgres state model

The server is effectively stateless beyond one Postgres table (device_requests). Rows are deleted after the CLI retrieves the token, or on next access if expired (15 minutes).

CREATE TABLE device_requests (
  device_code TEXT PRIMARY KEY,
  user_code TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'pending',  -- 'pending' or 'completed'
  access_token TEXT,
  api_url TEXT,
  organization_id TEXT,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Deployment and scaling

  • Works anywhere: any platform that runs Node.js and can reach Postgres (VPS, Railway, Fly.io, Render, etc.).
  • Horizontal scaling: safe as long as every instance shares the same DATABASE_URL, SERVER_URL, BROWSER_ORIGIN, and OAuth client credentials. No sticky sessions required.
  • Serverless: use a managed Postgres connection string appropriate for high concurrency (pooling).
  • Self-hosted production: deploy both agent/server and web; see Configuration - Agent → Common setup steps.

Next steps

Search documentation
Find a docs page
Discord Support