Dashboard authentication reference
Reference for the dashboard’s authentication environment variables, OAuth providers, API routes, session and API-key formats, and role permissions.
management-api serves every sign-in route below and decides who each API request is from, from the request’s own session cookie, proxy headers or API key. The dashboard is static files served by nginx: the Gateway or Ingress sends every /api path to management-api, and nginx proxies any that reaches it, with those credentials unchanged. The chart sets the variables below on management-api.
Environment variables
Section titled “Environment variables”Core settings
Section titled “Core settings”| Variable | Description | Default |
|---|---|---|
OMNIA_AUTH_MODE | Authentication mode: anonymous, proxy, oauth, or builtin | anonymous |
OMNIA_BASE_URL | Base URL for building OAuth callback and redirect URLs | http://localhost:3000 |
Session configuration
Section titled “Session configuration”| Variable | Description | Default |
|---|---|---|
OMNIA_SESSION_SECRET | 32+ character secret for session encryption | Required in production for oauth, builtin and proxy; a fixed development constant otherwise (see below) |
OMNIA_SESSION_COOKIE_NAME | Name of the session cookie | omnia_session |
OMNIA_SESSION_TTL | Session lifetime in seconds | 86400 (24 hours) |
Role mapping
Section titled “Role mapping”| Variable | Description | Default |
|---|---|---|
OMNIA_AUTH_ROLE_ADMIN_GROUPS | Comma-separated groups that get admin role | - |
OMNIA_AUTH_ROLE_EDITOR_GROUPS | Comma-separated groups that get editor role | - |
OMNIA_AUTH_ANONYMOUS_ROLE | Role for anonymous users | viewer |
Proxy mode settings
Section titled “Proxy mode settings”| Variable | Description | Default |
|---|---|---|
OMNIA_AUTH_PROXY_HEADER_USER | Header containing username | X-Forwarded-User |
OMNIA_AUTH_PROXY_HEADER_EMAIL | Header containing email | X-Forwarded-Email |
OMNIA_AUTH_PROXY_HEADER_GROUPS | Header containing groups (comma-separated) | X-Forwarded-Groups |
OMNIA_AUTH_PROXY_HEADER_DISPLAY_NAME | Header containing display name | X-Forwarded-Preferred-Username |
OAuth mode settings
Section titled “OAuth mode settings”| Variable | Description | Default |
|---|---|---|
OMNIA_OAUTH_PROVIDER | Provider type (see below) | generic |
OMNIA_OAUTH_CLIENT_ID | OAuth client ID | - |
OMNIA_OAUTH_CLIENT_SECRET | OAuth client secret | - |
OMNIA_OAUTH_CLIENT_SECRET_FILE | Path to file containing client secret | - |
OMNIA_OAUTH_ISSUER_URL | OIDC issuer URL (required for generic) | - |
OMNIA_OAUTH_SCOPES | Comma-separated OAuth scopes. Replaces the provider’s scope list entirely | Provider’s scopes (see OAuth providers) |
OAuth claim mapping
Section titled “OAuth claim mapping”Each variable overrides one claim; any claim left unset uses the provider’s mapping.
| Variable | Description | Default |
|---|---|---|
OMNIA_OAUTH_CLAIM_USERNAME | Claim for username | preferred_username (email for google) |
OMNIA_OAUTH_CLAIM_EMAIL | Claim for email | email |
OMNIA_OAUTH_CLAIM_DISPLAY_NAME | Claim for display name | name |
OMNIA_OAUTH_CLAIM_GROUPS | Claim for groups | groups |
The Helm chart sets these variables only when the matching dashboard.oauth.scopes or
dashboard.oauth.claims.* value is non-empty, and all of them default to empty.
Provider-specific settings
Section titled “Provider-specific settings”| Variable | Description | Default |
|---|---|---|
OMNIA_OAUTH_AZURE_TENANT_ID | Azure AD tenant ID | common |
OMNIA_OAUTH_OKTA_DOMAIN | Okta organization domain (required unless OMNIA_OAUTH_ISSUER_URL is set) | - |
API keys
Section titled “API keys”| Variable | Description | Default |
|---|---|---|
OMNIA_AUTH_API_KEYS_ENABLED | Enable API key authentication | true |
OMNIA_AUTH_API_KEYS_MAX_PER_USER | Maximum keys per user | 10 |
OMNIA_AUTH_API_KEYS_DEFAULT_EXPIRATION | Default expiration in days (0 = never) | 90 |
Builtin mode settings
Section titled “Builtin mode settings”| Variable | Description | Default |
|---|---|---|
POSTGRES_CONN | management-api’s own PostgreSQL database, which holds the builtin users. Required in builtin mode | - |
OMNIA_BUILTIN_POSTGRES_URL | The database a postgres API-key store falls back to when dashboard.apiKeys names none | - |
OMNIA_BUILTIN_ALLOW_SIGNUP | Allow public user registration | false |
OMNIA_BUILTIN_VERIFY_EMAIL | Require email verification | false |
OMNIA_BUILTIN_MIN_PASSWORD_LENGTH | Minimum password length | 8 |
OMNIA_BUILTIN_MAX_FAILED_ATTEMPTS | Failed logins before lockout | 5 |
OMNIA_BUILTIN_LOCKOUT_DURATION | Lockout duration in seconds | 900 |
OMNIA_BUILTIN_RESET_TOKEN_EXPIRATION | Password reset token TTL (seconds) | 3600 |
OMNIA_BUILTIN_VERIFICATION_TOKEN_EXPIRATION | Email verification token TTL (seconds) | 86400 |
OMNIA_BUILTIN_ADMIN_USERNAME | Initial admin username | - |
OMNIA_BUILTIN_ADMIN_EMAIL | Initial admin email | - |
OMNIA_BUILTIN_ADMIN_PASSWORD | Initial admin password | - |
management-api reads every variable in this table. The dashboard’s pages learn OMNIA_BUILTIN_ALLOW_SIGNUP, which opens /signup, and OMNIA_BUILTIN_MIN_PASSWORD_LENGTH, which the signup and reset forms show, from management-api’s GET /api/v1/config; a length that is not a whole number is 8 there too. The dashboard keeps no user store.
Admin seeding runs at management-api’s startup only when its user table is empty and all three of OMNIA_BUILTIN_ADMIN_USERNAME, OMNIA_BUILTIN_ADMIN_EMAIL and OMNIA_BUILTIN_ADMIN_PASSWORD are set. The Helm chart sets all three only when dashboard.auth.mode is builtin, and the first two default to admin and admin@example.com.
Password reset and verification email
Section titled “Password reset and verification email”management-api sends reset and verification links through SMTP. With OMNIA_SMTP_HOST unset, it sends nothing and logs a warning.
Both links start with OMNIA_BASE_URL. A reset link opens /reset-password?token=…, where the user sets a new password. A verification link opens /verify-email?token=…: the user selects Verify email, and the page sends the token to POST /api/v1/auth/builtin/verify-email. Neither page sends a Referer header, because its URL holds the token.
Besides /login, the builtin sign-in pages need no session: /signup, /forgot-password, /reset-password and /verify-email. Each one redirects to / outside builtin mode. /signup also redirects to /login when OMNIA_BUILTIN_ALLOW_SIGNUP is off, and sends a signed-in user to returnTo when it is a path on this origin, otherwise to /.
| Variable | Description | Default |
|---|---|---|
OMNIA_SMTP_HOST | SMTP server host | - |
OMNIA_SMTP_PORT | SMTP server port | 587 |
OMNIA_SMTP_SECURE | true for implicit TLS | false |
OMNIA_SMTP_FROM | Sender address | noreply@omnia.local |
OMNIA_SMTP_USER / OMNIA_SMTP_PASS | SMTP credentials; sent only when both are set | - |
Helm values
Section titled “Helm values”The chart renders the variables above from these values. Variables with no value here go in extra environment: the builtin token lifetimes in managementApi.podOverrides.extraEnv, and OMNIA_CSP_POLICY, which nginx reads, in dashboard.extraEnv or dashboard.extraEnvFrom.
| Variable | Helm value |
|---|---|
OMNIA_AUTH_MODE | dashboard.auth.mode (no default; the chart fails when it is empty) |
OMNIA_ALLOW_ANONYMOUS | dashboard.auth.allowAnonymous (must be true with anonymous mode) |
OMNIA_AUTH_ANONYMOUS_ROLE | dashboard.auth.anonymousRole |
OMNIA_SESSION_COOKIE_NAME / OMNIA_SESSION_TTL | dashboard.auth.cookieName / dashboard.auth.ttl |
OMNIA_AUTH_ROLE_ADMIN_GROUPS / OMNIA_AUTH_ROLE_EDITOR_GROUPS | dashboard.auth.roleMapping.adminGroups / .editorGroups (lists) |
OMNIA_BASE_URL | dashboard.baseUrl, else derived from dashboard.ingress.host and dashboard.ingress.tls |
OMNIA_AUTH_PROXY_HEADER_USER / _EMAIL / _GROUPS / _DISPLAY_NAME | dashboard.proxy.headerUser / .headerEmail / .headerGroups / .headerDisplayName |
OMNIA_OAUTH_PROVIDER, _ISSUER_URL, _SCOPES | dashboard.oauth.provider, .issuerUrl, .scopes |
OMNIA_OAUTH_AZURE_TENANT_ID / OMNIA_OAUTH_OKTA_DOMAIN | dashboard.oauth.azureTenantId / .oktaDomain |
OMNIA_OAUTH_CLAIM_USERNAME / _EMAIL / _DISPLAY_NAME / _GROUPS | dashboard.oauth.claims.username / .email / .displayName / .groups |
OMNIA_BUILTIN_ALLOW_SIGNUP, _VERIFY_EMAIL, _MIN_PASSWORD_LENGTH, _MAX_FAILED_ATTEMPTS, _LOCKOUT_DURATION | dashboard.builtin.allowSignup, .verifyEmail, .minPasswordLength, .maxFailedAttempts, .lockoutDuration |
OMNIA_SESSION_REDIS_URL | dashboard.session.redis, else redis.default. Required with managementApi.replicaCount above 1: the chart refuses to render more than one replica without it. Unset, one replica keeps sessions in memory |
OMNIA_BUILTIN_ADMIN_USERNAME / _EMAIL | dashboard.builtin.admin.username / .email |
OMNIA_SMTP_HOST, _PORT, _SECURE, _FROM, _USER | dashboard.builtin.smtp.host, .port, .secure, .from, .user. Set only when host is set, on management-api, which sends the email |
OMNIA_AUTH_API_KEYS_ENABLED, _MAX_PER_USER, _DEFAULT_EXPIRATION | dashboard.apiKeys.enabled, .maxPerUser, .defaultExpiration |
Secret-backed values:
| Value | Secret key | Variable |
|---|---|---|
dashboard.auth.existingSessionSecret | OMNIA_SESSION_SECRET | OMNIA_SESSION_SECRET. Left empty, the chart generates a secret on first install and keeps it across upgrades. Not set in anonymous mode. |
dashboard.oauth.existingSecret | OMNIA_OAUTH_CLIENT_ID, OMNIA_OAUTH_CLIENT_SECRET | the same names |
managementApi.database.existingSecret | POSTGRES_CONN | POSTGRES_CONN, on management-api. Required in builtin mode: the chart refuses to render without it |
dashboard.builtin.existingPostgresSecret | OMNIA_BUILTIN_POSTGRES_URL | OMNIA_BUILTIN_POSTGRES_URL |
dashboard.builtin.admin.existingSecret | OMNIA_BUILTIN_ADMIN_PASSWORD | OMNIA_BUILTIN_ADMIN_PASSWORD, on management-api |
dashboard.builtin.smtp.existingSecret | OMNIA_SMTP_PASS | OMNIA_SMTP_PASS, on management-api |
Removed and deprecated values:
dashboard.builtin.storeTypeanddashboard.builtin.sqlitePathare removed. They chose the dashboard’s own user store, and builtin users now live in management-api’s database. Left in a values file, they render and do nothing, and the install notes say so.dashboard.persistenceis deprecated, and nothing mounts it. It was the dashboard’s SQLite volume. For one release the chart still renders<release>-dashboard-data, whenenabledistrueandexistingClaimis empty, annotatedhelm.sh/resource-policy: keep, so an upgrade from v0.9.0-beta.20, which created it without that annotation, does not delete the old users. Such an install must upgrade through this release rather than skip it, so the claim gets the annotation before a later release stops rendering it. Delete the claim yourself once you will not roll back.- The session Secret,
<release>-dashboard-session-secret, is management-api’s. It keeps its name, so an upgrade keeps every session.
OAuth providers
Section titled “OAuth providers”The OMNIA_OAUTH_PROVIDER values are generic, google, azure, okta and cognito. Every provider discovers its configuration via <issuer>/.well-known/openid-configuration.
Generic OIDC
Section titled “Generic OIDC”For any OpenID Connect compliant provider.
OMNIA_OAUTH_PROVIDER=genericOMNIA_OAUTH_ISSUER_URL=https://auth.example.comRequired: OMNIA_OAUTH_ISSUER_URL
Scopes: openid profile email groups
OMNIA_OAUTH_PROVIDER=googleIssuer: https://accounts.google.com
Scopes: openid profile email. Username comes from the email claim, because Google does not issue preferred_username.
Google’s ID tokens carry no groups claim, for consumer accounts and Google Workspace alike, so group role mapping and workspace roleBindings match nothing. Grant access by email with workspace directGrants.
Console: Google Cloud Console
Azure AD / Entra ID
Section titled “Azure AD / Entra ID”OMNIA_OAUTH_PROVIDER=azureOMNIA_OAUTH_AZURE_TENANT_ID=your-tenant-idIssuer: https://login.microsoftonline.com/{tenant}/v2.0
Scopes: openid profile email
Console: Azure Portal
Group claims: added under Token Configuration > Add groups claim
OMNIA_OAUTH_PROVIDER=oktaOMNIA_OAUTH_OKTA_DOMAIN=your-domain.okta.comIssuer: https://{domain}, the Okta org authorization server. For a custom authorization server such as https://{domain}/oauth2/default, set OMNIA_OAUTH_ISSUER_URL.
Scopes: openid profile email groups
Console: Okta Admin Console > Applications
Amazon Cognito
Section titled “Amazon Cognito”OMNIA_OAUTH_PROVIDER=cognitoOMNIA_OAUTH_ISSUER_URL=https://cognito-idp.<region>.amazonaws.com/<userPoolId>Required: OMNIA_OAUTH_ISSUER_URL
Scopes: openid profile email. Cognito rejects an unknown scope, so the preset never requests groups.
Claims: username from cognito:username, groups from cognito:groups. Role mapping and workspace roleBindings use Cognito group names.
Logout: Cognito publishes no end_session_endpoint, so logout clears the Omnia session only.
Console: Amazon Cognito > User pools > App clients
API endpoints
Section titled “API endpoints”GET /api/auth/login?returnTo=/agentsInitiates OAuth flow. Query parameter returnTo specifies redirect after login. Only a place on OMNIA_BASE_URL’s origin is followed; any other value, such as another host, //host or javascript:, sends the user to /.
Callback
Section titled “Callback”GET /api/auth/callback?code=xxx&state=xxxOAuth callback endpoint. Handles authorization code exchange.
Logout
Section titled “Logout”POST /api/auth/logoutClears session. In OAuth mode, may redirect to IdP for SSO logout.
Response:
{ "success": true, "redirectUrl": "https://idp.example.com/logout?..." // Optional}Session
Section titled “Session”GET /api/auth/sessionReturns whether the request is authenticated. It does not return user details.
Response:
{ "authenticated": true }API keys
Section titled “API keys”GET /api/settings/api-keysPOST /api/settings/api-keysDELETE /api/settings/api-keys/:idManage API keys for the current user.
Builtin authentication
Section titled “Builtin authentication”POST /api/auth/builtin/loginAuthenticate with username/email and password.
Request:
{ "username": "user@example.com", "password": "secret123"}Response:
{ "success": true, "user": { "id": "user-123", "username": "jdoe", "email": "jdoe@example.com", "role": "editor" }}Returns an error message string on failure. A locked account also sets locked: true; an unverified email also sets emailNotVerified: true.
Signup
Section titled “Signup”POST /api/auth/builtin/signupRegister a new user (if OMNIA_BUILTIN_ALLOW_SIGNUP=true).
Request:
{ "username": "jdoe", "email": "jdoe@example.com", "password": "secret123", "displayName": "John Doe"}Returns an error message string on failure; a field-specific failure also sets field to the offending field name (for example "email").
Forgot password
Section titled “Forgot password”POST /api/auth/builtin/forgot-passwordRequest a password reset token.
Request:
{ "email": "jdoe@example.com"}Response: Always returns success (prevents email enumeration).
Reset password
Section titled “Reset password”POST /api/auth/builtin/reset-passwordSet new password using reset token.
Request:
{ "token": "reset-token-from-email", "password": "newpassword123"}Returns an error message string on failure.
Verify email
Section titled “Verify email”POST /api/auth/builtin/verify-emailVerify email address using verification token.
Request:
{ "token": "verification-token-from-email"}Returns an error message string on failure.
Session data structure
Section titled “Session data structure”The session cookie holds only { sid }. management-api writes the full record server-side under that ID and is the only reader. The session store holds the record, and a separate record for in-flight PKCE state. The store is Redis when OMNIA_SESSION_REDIS_URL is set, which every replica shares, and management-api’s memory otherwise.
interface SessionRecord { user: User; oauth?: OAuthTokens; createdAt: number; // wall-clock ms; diagnostic only}
interface User { id: string; username: string; email?: string; displayName?: string; groups: string[]; role: "admin" | "editor" | "viewer"; provider: "anonymous" | "proxy" | "oauth" | "builtin";}
interface OAuthTokens { refreshToken?: string; idToken?: string; expiresAt?: number; // Unix seconds provider: "generic" | "google" | "azure" | "okta";}
// Stored under the IdP state value; consumed once at callback.interface PkceRecord { codeVerifier: string; codeChallenge: string; state: string; returnTo?: string; createdAt: number;}User roles
Section titled “User roles”Permissions matrix
Section titled “Permissions matrix”The platform role governs cross-workspace actions only. Everything inside a workspace is decided by the caller’s role in that workspace, so multi-tenancy follows workspace boundaries.
Platform role (from OMNIA_AUTH_ROLE_*_GROUPS):
| Action | Viewer | Editor | Admin |
|---|---|---|---|
| Manage own API keys | Yes | Yes | Yes |
| View all users’ API keys | No | No | Yes |
| View and edit dashboard settings | No | No | Yes |
| Manage users | No | No | Yes |
| List credentials across every workspace | No | No | Yes |
| Manage credentials in a namespace no workspace owns | No | No | Yes |
| Manage access bindings on any workspace | No | No | Yes |
A platform editor has no rights beyond a viewer: editing happens inside a
workspace, under the workspace role.
Workspace role (from the Workspace’s roleBindings and directGrants):
| Action | Viewer | Editor | Owner |
|---|---|---|---|
| View agents, prompt packs, tools, sessions, logs, metrics | Yes | Yes | Yes |
| View the workspace’s providers | Yes | Yes | Yes |
| View the workspace’s credentials (metadata only) | No | Yes | Yes |
| Deploy, scale and delete agents | No | Yes | Yes |
| Modify prompt packs and tools | No | Yes | Yes |
| Create, update and delete the workspace’s credentials | No | No | Yes |
Change a provider’s credential (secretRef) | No | No | Yes |
| Manage workspace members | No | No | Yes |
Credential and provider routes are addressed by workspace
(/api/workspaces/{name}/secrets, /api/workspaces/{name}/providers/{provider}),
never by namespace, and make their Kubernetes calls as the caller’s role in
that workspace. The viewer role holds no access to Secrets, since reading one
is reading its values, so credentials are listed from the editor up; only the
owner role may write Secrets and Providers, so only an owner changes them. A platform role never grants access to a workspace’s
data. A platform admin can self-grant a workspace role through
Settings → Access like anyone else.
Role resolution order
Section titled “Role resolution order”- Check admin groups
- Check editor groups
- Default to viewer
First match wins. Example:
OMNIA_AUTH_ROLE_ADMIN_GROUPS=admins,super-usersOMNIA_AUTH_ROLE_EDITOR_GROUPS=developers,opsUser with groups ["developers", "admins"] gets admin role (checked first).
API key format
Section titled “API key format”API keys are omnia_sk_ followed by 32 random bytes, base64url-encoded:
omnia_sk_[base64url-random]The key ID, user ID, creation timestamp and a bcrypt hash of the key are stored as separate columns in the key store. Authenticating a request bcrypt-compares the presented key against every non-expired key’s stored hash; nothing is decoded from the key itself.
HTTP headers
Section titled “HTTP headers”Request headers
Section titled “Request headers”| Header | Description |
|---|---|
Authorization | Bearer token: Bearer omnia_sk_... |
X-API-Key | Alternative API key header |
Cookie | Session cookie (browser requests) |
Error codes
Section titled “Error codes”OAuth errors
Section titled “OAuth errors”| Error | Description |
|---|---|
invalid_state | CSRF state mismatch |
no_code | No authorization code in callback |
callback_failed | Token exchange failed |
access_denied | User denied consent |
invalid_claims | Missing required claims |
config_error | OAuth misconfiguration |
API errors
Section titled “API errors”| Status | Error | Source | Description |
|---|---|---|---|
| 401 | unauthenticated | management-api sign-in gate | No valid session or API key, or a credential that does not check out |
| 401 | unauthorized | management-api caller resolution | The caller could not be resolved, for example with the session store unreachable |
| 403 | Forbidden | management-api operation guard | Insufficient permissions |
Security headers
Section titled “Security headers”Pages and API answers carry different headers, because different services send them.
Pages come from the dashboard’s nginx, which sends these headers on every page and static file. The token pages, /reset-password and /verify-email, send Referrer-Policy: no-referrer instead.
| Header | Value |
|---|---|
Strict-Transport-Security | max-age=63072000; includeSubDomains; preload |
Content-Security-Policy | default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' ws: wss:; worker-src 'self' blob:; media-src 'self' blob: data:; frame-ancestors 'none'; base-uri 'self'; form-action 'self' |
X-Frame-Options | DENY |
X-Content-Type-Options | nosniff |
Referrer-Policy | strict-origin-when-cross-origin |
Permissions-Policy | camera=(), microphone=(self), geolocation=(self), payment=() |
OMNIA_CSP_POLICY, on the dashboard container, replaces the Content-Security-Policy value wholesale. The other page headers have no override.
API answers come from management-api. The chart’s HTTPRoute and Ingress send every /api path straight to it, and it sets its own headers on every answer:
| Header | Value |
|---|---|
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
Strict-Transport-Security | max-age=63072000; includeSubDomains; preload, only when OMNIA_BASE_URL is HTTPS |
nginx’s own proxy applies only to /api traffic that reaches the dashboard Service, for example behind a proxy of your own that sends everything there. Those answers carry nginx’s page headers instead of management-api’s.
Session cookies are HttpOnly; SameSite=Lax; Path=/, plus Secure in production.
Callback URLs
Section titled “Callback URLs”management-api uses these URLs for the IdP redirect:
| Provider | Redirect URI |
|---|---|
| All | https://your-domain/api/auth/callback |
Post-logout redirect:
| Provider | Post-Logout URI |
|---|---|
| All | https://your-domain/login |