Skip to content

Authentication

lqdflying edited this page Oct 8, 2026 · 20 revisions

Authentication

ChatHub requires DATABASE_URL + DATABASE_DRIVER (PostgreSQL). Auth is required for Docker deployments -- the container refuses to start without at least one of NEXT_PUBLIC_ENABLE_NEXT_AUTH, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, or AUTH_TOKEN set.


1. Credentials Login (browser -- single-user, no OAuth needed)

A built-in username/password or token login page, powered by NextAuth's Credentials provider. No external OAuth service is required.

IMPORTANT: This mode is env-backed, not DB-backed. The username/password or token is validated directly from environment variables and is not stored or managed in PostgreSQL.

It is acceptable for testing, bootstrap access, or simple single-admin setups, but it is not recommended as the primary auth mode for formal production or multi-user deployments. For those cases, prefer OAuth / OIDC providers via NextAuth.

NextAuth browser sessions persist in a cookie. Use AUTH_SESSION_MAX_AGE_DAYS to control how long that login cookie remains valid. Default is 30 days.

Mobile resume behavior

Returning to ChatHub after a mobile browser or installed PWA has been in the background does not trigger an automatic NextAuth session check solely because the window regained focus or the network came back online. This prevents a brief resume-time network failure from being mistaken for a sign-out and clearing the current assistant, Image Settings, or other account state.

ChatHub initializes the browser from the session already read by the server. It does not issue a second cookie-writing session request after a reload. Normal page navigation, session checks, and ordinary session reads are cookie-neutral. Sign-in, sign-out, OAuth callbacks, and a controlled keep-alive remain able to update the session cookie.

ChatHub performs a read-only session check immediately after startup and after an authentication change. While an authenticated browser reports an online connection, it repeats the check every five minutes. If the check confirms that the user signed out or changed accounts, ChatHub updates the page to that confirmed session without a reload. Network failures, unsuccessful responses, invalid responses, and malformed sessions preserve the last confirmed state.

When the five-minute check still matches the active account, ChatHub renews the session expiry through one serialized keep-alive request. Session renewal and sign-in/sign-out writes cannot overlap; a replacement login waits for an older renewal and then becomes the final cookie value. Other open tabs reconcile when the authentication change finishes. During an OAuth sign-in, background renewal pauses before Auth.js begins provider lookup, CSRF retrieval, and its sign-in request. After Auth.js creates the state and PKCE transaction and returns the provider authorization URL, ChatHub renews the guard for the full 15-minute callback window and only then leaves for the identity provider. A slow preflight therefore does not consume callback time. Auth.js refreshes the current browser session once during this preflight; ChatHub keeps the OAuth guard active through that refresh and the following navigation. The callback or error page clears the pending state only after authentication resolves in the new page, and stale cleanup does not begin before the callback window expires.

If browser privacy controls make localStorage unavailable, ChatHub still opens the OAuth provider instead of silently stopping. The initiating page remains protected from its own background renewal during navigation, although tabs cannot coordinate with one another without shared browser storage. This preserves sliding expiry for supported browsers while preventing delayed session responses from restoring an older account. Browsers without the required cross-tab locking support skip background renewal rather than issuing an unsafe cookie write.

A resume can also interrupt an ordinary data read. ChatHub retries that read and does not stack Request failed cards for it. A failed save still shows one Request failed card, so a setting on screen after that card was not confirmed. HTTP 401 opens sign-in. Any other received HTTP 400 or higher response still shows one card, including when the error body itself is cut off.

For a detailed flow diagram of the credentials login process, see Credentials Login Flow.

Option A -- Username and Password

NEXT_PUBLIC_ENABLE_NEXT_AUTH=1
NEXT_AUTH_SECRET=<your-secret>
NEXT_AUTH_SSO_PROVIDERS=credentials
AUTH_SESSION_MAX_AGE_DAYS=7
AUTH_CREDENTIALS_USERNAME=admin
AUTH_CREDENTIALS_PASSWORD=your-strong-password
AUTH_USER_ID=default_user           # optional -- defaults to "credentials_user"

Option B -- Token only

NEXT_PUBLIC_ENABLE_NEXT_AUTH=1
NEXT_AUTH_SECRET=<your-secret>
NEXT_AUTH_SSO_PROVIDERS=credentials
AUTH_SESSION_MAX_AGE_DAYS=7
AUTH_TOKEN=your-secret-token
AUTH_USER_ID=default_user           # optional -- defaults to "credentials_user"

Option C -- Both methods enabled

NEXT_PUBLIC_ENABLE_NEXT_AUTH=1
NEXT_AUTH_SECRET=<your-secret>
NEXT_AUTH_SSO_PROVIDERS=credentials
AUTH_SESSION_MAX_AGE_DAYS=7
AUTH_CREDENTIALS_USERNAME=admin
AUTH_CREDENTIALS_PASSWORD=your-strong-password
AUTH_TOKEN=your-secret-token
AUTH_USER_ID=default_user

NOTE: AUTH_TOKEN can also be used for machine-to-machine API access. Send Authorization: Bearer <token> in API requests. The same token works for both browser login and API calls. The server validates the bearer on every API request and assigns the request to AUTH_USER_ID (or the direct-API fallback default_user); callers cannot choose another user with custom identity headers. Do not send or trust X-token-auth-user—ChatHub ignores it as untrusted input.

IMPORTANT: WebAPI authentication fails closed whenever Clerk, NextAuth, or AUTH_TOKEN authentication is configured. Each API request must present a valid configured browser session or bearer token; provider API keys and access codes inside the encrypted request payload cannot bypass a missing or invalid server credential. ChatHub validates NextAuth sessions directly at the API route and ignores the legacy X-oauth-authorized header. Authenticated requests are always assigned to the database owner returned by Clerk/NextAuth or to AUTH_USER_ID for bearer authentication.

When ENABLE_OIDC=1, direct OIDC clients may send a valid ChatHub-issued access token in the Oidc-Auth header. Protected WebAPI routes validate that token at the same shared route boundary and assign the request to its sub. An invalid supplied OIDC token is rejected and cannot fall back to an access code or provider API key. Setting ENABLE_OIDC=0 disables this authentication path even if OIDC_JWKS_KEY remains configured; previously issued OIDC tokens then grant no WebAPI identity, and the route applies the remaining configured authentication or access-code rules.

This boundary includes OpenAI text-to-speech and speech-to-text, the image proxy, API Tester, and trace-event endpoints. ChatHub's browser clients attach the standard encrypted authentication payload automatically. Direct API clients must also provide the configured session or Authorization: Bearer <AUTH_TOKEN>; calling these endpoints without it is rejected before OpenAI client creation, outbound fetching, API probing, or trace mutation.

When all server authentication modes are disabled, access-code mode remains available. OpenAI TTS/STT may also use the encrypted OpenAI provider key because the same operation consumes it. Provider API keys do not authorize the image proxy, API Tester, or trace endpoints.

For OpenAI TTS/STT, a custom browser baseURL is honored only together with that browser's OpenAI API key. When ChatHub uses the deployment's OPENAI_API_KEY, it sends the key only to the deployment-controlled OPENAI_PROXY_URL or OpenAI's default endpoint; a browser-provided endpoint cannot receive the server key.

Custom plugin validation with Use proxy also uses this authenticated boundary. ChatHub automatically attaches the encrypted browser authentication to both the manifest and its referenced OpenAPI document, so access-code deployments do not require a separate manual credential for these proxy requests.

IMPORTANT: The credentials login form only appears when NEXT_AUTH_SSO_PROVIDERS explicitly includes credentials. If you set AUTH_CREDENTIALS_USERNAME / AUTH_CREDENTIALS_PASSWORD but leave NEXT_AUTH_SSO_PROVIDERS=github, the sign-in page will only show GitHub.

Session age

AUTH_SESSION_MAX_AGE_DAYS applies to NextAuth browser sessions, including credentials login and OAuth login:

  • AUTH_SESSION_MAX_AGE_DAYS=1 → expire after 1 day
  • AUTH_SESSION_MAX_AGE_DAYS=7 → expire after 7 days
  • AUTH_SESSION_MAX_AGE_DAYS=30 → default

2. Credentials + OAuth (combine both on one login page)

You can enable credentials alongside OAuth providers. The login page will show both the username/password form and the OAuth buttons:

NEXT_PUBLIC_ENABLE_NEXT_AUTH=1
NEXT_AUTH_SECRET=<your-secret>
NEXT_AUTH_SSO_PROVIDERS=credentials,github
AUTH_SESSION_MAX_AGE_DAYS=7
AUTH_CREDENTIALS_USERNAME=admin
AUTH_CREDENTIALS_PASSWORD=your-strong-password
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...

TIP: Use NEXT_AUTH_SSO_PROVIDERS=credentials,github when you want both the credentials form and GitHub on the same sign-in page.


3. NextAuth / OAuth only (single or multi-user)

Configure an OAuth provider (Auth0, GitHub, Authentik, Zitadel, etc.) via NextAuth:

NEXT_PUBLIC_ENABLE_NEXT_AUTH=1
NEXT_AUTH_SECRET=<your-secret>
NEXT_AUTH_SSO_PROVIDERS=auth0        # or github, authentik, zitadel, ...
AUTH_SESSION_MAX_AGE_DAYS=7          # optional, default 30
# Provider-specific vars (example: Auth0)
AUTH_AUTH0_ID=...
AUTH_AUTH0_SECRET=...
AUTH_AUTH0_ISSUER=https://your-domain.auth0.com

GitHub sign-in fails with Configuration / unexpected iss

If GitHub login returns to /next-auth/error and the container logs include:

OperationProcessingError: unexpected "iss" (issuer) response parameter value
expected: "https://authjs.dev"

the running image predates ChatHub's GitHub issuer pin. GitHub now sends RFC 9207 iss=https://github.com/login/oauth on the OAuth callback. Auth.js 5.0.0-beta.30 compares that value to the placeholder https://authjs.dev unless the GitHub provider declares the issuer.

ChatHub sets the GitHub provider issuer to https://github.com/login/oauth. Upgrade to an image that includes that change. Do not set the issuer to https://github.com (wrong value). APP_URL is still the public ChatHub origin used for the GitHub OAuth app callback (https://<your-host>/api/auth/callback/github); it is not the issuer.


4. Credentials Login Flow

For a detailed technical explanation and mermaid diagram of the credentials login flow, see doc/credentials-login-flow.md in the repository.


5. Recover settings after an account-loading failure

Settings pages that require account data keep their loading indicator only while the current request is pending. If loading fails, select Retry; a force refresh should not be necessary. This recovery guard includes General, Chat Instruction, Default Assistant, legacy LLM, AI Provider, Image, TTS, Hotkeys, System Assistant, MCP, and Storage so their account-owned actions cannot run before the account is verified. Account-independent pages such as About remain usable while those settings recover.

If ChatHub shows Sign in again instead, the signed-in identity is unresolved or does not match the returned account data. Use that action rather than retrying so ChatHub can establish a consistent session before loading account-owned settings.

Clone this wiki locally