Setup & Auth
Environment
Authorization: Bearer lattice_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxLATTICE_MANAGEMENT_API_KEY and LATTICE_GATEWAY_URL are read only on the server (server/utils/env.ts, server/utils/gateway-client.ts) and never sent to the browser. DB_* points at the dashboard's own Postgres database — used exclusively for better-auth's user/session/account tables, entirely separate from the gateway's database. See Configuration Reference for the full variable list.
type ProviderAdapter = {
id: ProviderId;
createChatCompletion(
request: ChatCompletionRequest,
ctx: ProviderRequestContext,
): Promise<ChatCompletionResponse>;
streamChatCompletion(
request: ChatCompletionRequest,
ctx: ProviderRequestContext,
): AsyncGenerator<ChatCompletionChunk, void, void>;
};
type ProviderRequestContext = {
apiKey?: string;
providerModel: string;
signal: AbortSignal;
};Sign-in
Email/password only (emailAndPassword: { enabled: true } — no social/OAuth providers configured). The login page (/login) is guest-gated (definePageMeta({ auth: 'guest' })); every other page requires an authenticated session (definePageMeta({ auth: 'user' })), enforced both client-side by route meta and server-side — every server/api/** handler calls requireUserSession(event) before doing anything else.
Session lifetime
{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1755878400,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "..." },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 42,
"completion_tokens": 118,
"total_tokens": 160
}
}Deliberately short. See the next section for why.
Secure cookies
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1755878400,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1755878400,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"Sure"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1755878400,"model":"gpt-4o","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":42,"completion_tokens":118,"total_tokens":160}}
data: [DONE]This is explicit even though it matches better-auth's own default — a guard against that upstream default silently changing in a future version, not a behavior change from the library's out-of-the-box setting.
The flat, single-tenant trust model
This is the security property worth understanding before provisioning dashboard accounts: every dashboard session is full admin over every application. There are no per-user roles, no per-application scoping, no read-only accounts. Signing in grants the same access the management API key itself grants — the dashboard is, architecturally, just a UI over that one key, shared by every signed-in user.
This is why the session lifetime is short (12 hours, hourly-refreshed) rather than the weeks-long default many admin tools use: a stolen dashboard session is equivalent to a stolen management key for as long as it's valid, so limiting that window is the primary mitigation available at this layer. If your organization needs per-admin scoping or audit trails distinguishing which admin took an action, that isn't modeled today — every write the dashboard makes to the gateway is indistinguishable from any other admin's, since the gateway itself only sees "a valid management key," not which dashboard user was signed in.
What the dashboard's database actually stores
Four tables, all better-auth's own core schema (not custom to this project): user, session, account (holds the hashed password for email/password auth), verification. Nothing about applications, API keys, usage, or request logs lives here — that's all on the gateway's side, fetched live through gatewayFetch on every page load.