Skip to content

Architecture

This page explains the internal mechanisms of UseBetter Console: the request flow through the middleware and core, how routing and authentication work, and the security model.

When a request arrives at your Hono app, it passes through the Console middleware:

  1. Middleware interceptcreateConsoleMiddleware() checks if the URL starts with /.well-known/better/. If not, the request passes through to your normal routes.
  2. Request conversion — the middleware converts the Hono Request into a ConsoleRequest (method, path, headers, query, body) and strips the /.well-known/better prefix from the path.
  3. Route matchingConsoleRouter.match() finds a registered route matching the method and path. If no route matches, a 404 is returned.
  4. CORS — for OPTIONS requests, preflight CORS headers are returned immediately (204). For all other requests, CORS headers are added to the response based on allowedOrigins.
  5. Authentication — if the route requires auth (requiresAuth: true), the router extracts the Authorization: Bearer <token> header, verifies the JWT, checks permissions, and attaches the session to the request.
  6. Handler execution — the matched handler receives the enriched request and returns a ConsoleResponse. The middleware converts it back to a standard Response.

The Hono middleware is intentionally minimal. It performs only two tasks:

  • Convert between Hono’s Request/Response and Console’s ConsoleRequest/ConsoleResponse
  • Route requests that start with the base path (/.well-known/better/) to handleConsoleRequest()

All routing, authentication, CORS handling, and error recovery live inside the core handleConsoleRequest() function. This design means:

  • The middleware has no knowledge of routes, auth, or session logic
  • Adding support for other frameworks (Express, Fastify) requires only a thin adapter
  • Testing the core does not require an HTTP server

ConsoleRouter is a simple pattern-matching router that registers two kinds of routes:

Built-in routes registered at startup, prefixed with /console/:

GET /console/health
GET /console/capabilities
POST /console/session/init
POST /console/session/verify (only with adapter)
GET /console/session/poll (only with adapter)
POST /console/session/claim (only with adapter)

Console routes are unauthenticated — they handle the session handshake and health checks.

Registered via registerProduct(), prefixed with /<productId>/:

GET /tenant/tenants
GET /tenant/tenants/:id
POST /tenant/tenants
DELETE /tenant/tenants/:id

Product routes are always authenticated and require a valid session token with the specified permission level.

Routes support :param segments for dynamic path parameters:

Pattern: /tenant/tenants/:id
Actual: /tenant/tenants/550e8400-e29b-41d4-a716-446655440000
Params: { id: "550e8400-e29b-41d4-a716-446655440000" }

The router performs exact segment matching — the pattern and actual path must have the same number of segments.

In auto-approve mode, initSession() signs a JWT immediately using the connection token secret. The JWT payload contains:

  • sessionId — random UUID
  • email — from the request body (defaults to "dev@localhost")
  • permissions — from allowedActions config
  • expiresAt — current time + tokenLifetime

No database interaction occurs. The JWT is verified on each subsequent request by decoding and checking the signature and expiry.

Magic link sessions use the database adapter for persistent storage:

  1. Init — generates a random 6-character code, SHA-256 hashes it, stores a ConsoleMagicLink record with a unique sessionId, and sends the raw code to the user’s email.
  2. Verify — the user submits the code. The server hashes it and compares against the stored hash. Failed attempts are tracked; after maxAttempts failures, the magic link is locked.
  3. Claim — creates a ConsoleSession record in the database with a new session token hash. Returns a signed JWT to the client. The claim is idempotent — it uses WHERE token_hash IS NULL to prevent double-claiming.

Session verification on subsequent requests: the JWT is decoded, the token hash is computed, and the session is looked up in the database by token hash. If the session exists and hasn’t expired, the request proceeds.

CORS is handled at the core level, not in the middleware. Every response from handleConsoleRequest() includes CORS headers when the request’s Origin header matches an entry in allowedOrigins.

  • Preflight (OPTIONS) — returns 204 with Access-Control-Allow-Origin, Access-Control-Allow-Methods (GET, POST, PATCH, DELETE, PUT, OPTIONS), Access-Control-Allow-Headers (Content-Type, Authorization), and Access-Control-Max-Age.
  • Normal requestsAccess-Control-Allow-Origin is set to the matched origin. If no origin matches, no CORS headers are added (the browser blocks the request).

The default allowed origin is https://console.usebetter.dev.

The connection token is never stored in plaintext. During init, the CLI generates a random token and its SHA-256 hash. Only the hash (sha256:<hex>) is stored in the environment. The raw token is shown once and then discarded.

At runtime, the hash is used as the JWT signing secret. This means:

  • The JWT cannot be forged without knowing the hash
  • Rotating the hash invalidates all existing JWTs
  • The original raw token is not needed after setup

In production (NODE_ENV is not "development" or "test"), the connection token secret must be at least 32 characters. Shorter secrets throw ConsoleWeakSecretError at startup.

Magic link code verification tracks failed attempts per magic link. After maxAttempts (default 5), the magic link is locked — no further verification attempts are accepted. The user must initiate a new session.

Magic link codes expire after 10 minutes regardless of attempts.

By default, only https://console.usebetter.dev can make cross-origin requests to your console endpoints. This prevents unauthorized frontends from accessing your data.

Auto-approve is blocked in production via ConsoleAutoApproveInProductionError. This prevents accidental deployment of a configuration that grants instant admin access to anyone.