Skip to content

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.

VariableDescriptionDefault
OMNIA_AUTH_MODEAuthentication mode: anonymous, proxy, oauth, or builtinanonymous
OMNIA_BASE_URLBase URL for building OAuth callback and redirect URLshttp://localhost:3000
VariableDescriptionDefault
OMNIA_SESSION_SECRET32+ character secret for session encryptionRequired in production for oauth, builtin and proxy; a fixed development constant otherwise (see below)
OMNIA_SESSION_COOKIE_NAMEName of the session cookieomnia_session
OMNIA_SESSION_TTLSession lifetime in seconds86400 (24 hours)
VariableDescriptionDefault
OMNIA_AUTH_ROLE_ADMIN_GROUPSComma-separated groups that get admin role-
OMNIA_AUTH_ROLE_EDITOR_GROUPSComma-separated groups that get editor role-
OMNIA_AUTH_ANONYMOUS_ROLERole for anonymous usersviewer
VariableDescriptionDefault
OMNIA_AUTH_PROXY_HEADER_USERHeader containing usernameX-Forwarded-User
OMNIA_AUTH_PROXY_HEADER_EMAILHeader containing emailX-Forwarded-Email
OMNIA_AUTH_PROXY_HEADER_GROUPSHeader containing groups (comma-separated)X-Forwarded-Groups
OMNIA_AUTH_PROXY_HEADER_DISPLAY_NAMEHeader containing display nameX-Forwarded-Preferred-Username
VariableDescriptionDefault
OMNIA_OAUTH_PROVIDERProvider type (see below)generic
OMNIA_OAUTH_CLIENT_IDOAuth client ID-
OMNIA_OAUTH_CLIENT_SECRETOAuth client secret-
OMNIA_OAUTH_CLIENT_SECRET_FILEPath to file containing client secret-
OMNIA_OAUTH_ISSUER_URLOIDC issuer URL (required for generic)-
OMNIA_OAUTH_SCOPESComma-separated OAuth scopes. Replaces the provider’s scope list entirelyProvider’s scopes (see OAuth providers)

Each variable overrides one claim; any claim left unset uses the provider’s mapping.

VariableDescriptionDefault
OMNIA_OAUTH_CLAIM_USERNAMEClaim for usernamepreferred_username (email for google)
OMNIA_OAUTH_CLAIM_EMAILClaim for emailemail
OMNIA_OAUTH_CLAIM_DISPLAY_NAMEClaim for display namename
OMNIA_OAUTH_CLAIM_GROUPSClaim for groupsgroups

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.

VariableDescriptionDefault
OMNIA_OAUTH_AZURE_TENANT_IDAzure AD tenant IDcommon
OMNIA_OAUTH_OKTA_DOMAINOkta organization domain (required unless OMNIA_OAUTH_ISSUER_URL is set)-
VariableDescriptionDefault
OMNIA_AUTH_API_KEYS_ENABLEDEnable API key authenticationtrue
OMNIA_AUTH_API_KEYS_MAX_PER_USERMaximum keys per user10
OMNIA_AUTH_API_KEYS_DEFAULT_EXPIRATIONDefault expiration in days (0 = never)90
VariableDescriptionDefault
POSTGRES_CONNmanagement-api’s own PostgreSQL database, which holds the builtin users. Required in builtin mode-
OMNIA_BUILTIN_POSTGRES_URLThe database a postgres API-key store falls back to when dashboard.apiKeys names none-
OMNIA_BUILTIN_ALLOW_SIGNUPAllow public user registrationfalse
OMNIA_BUILTIN_VERIFY_EMAILRequire email verificationfalse
OMNIA_BUILTIN_MIN_PASSWORD_LENGTHMinimum password length8
OMNIA_BUILTIN_MAX_FAILED_ATTEMPTSFailed logins before lockout5
OMNIA_BUILTIN_LOCKOUT_DURATIONLockout duration in seconds900
OMNIA_BUILTIN_RESET_TOKEN_EXPIRATIONPassword reset token TTL (seconds)3600
OMNIA_BUILTIN_VERIFICATION_TOKEN_EXPIRATIONEmail verification token TTL (seconds)86400
OMNIA_BUILTIN_ADMIN_USERNAMEInitial admin username-
OMNIA_BUILTIN_ADMIN_EMAILInitial admin email-
OMNIA_BUILTIN_ADMIN_PASSWORDInitial 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.

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 /.

VariableDescriptionDefault
OMNIA_SMTP_HOSTSMTP server host-
OMNIA_SMTP_PORTSMTP server port587
OMNIA_SMTP_SECUREtrue for implicit TLSfalse
OMNIA_SMTP_FROMSender addressnoreply@omnia.local
OMNIA_SMTP_USER / OMNIA_SMTP_PASSSMTP credentials; sent only when both are set-

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.

VariableHelm value
OMNIA_AUTH_MODEdashboard.auth.mode (no default; the chart fails when it is empty)
OMNIA_ALLOW_ANONYMOUSdashboard.auth.allowAnonymous (must be true with anonymous mode)
OMNIA_AUTH_ANONYMOUS_ROLEdashboard.auth.anonymousRole
OMNIA_SESSION_COOKIE_NAME / OMNIA_SESSION_TTLdashboard.auth.cookieName / dashboard.auth.ttl
OMNIA_AUTH_ROLE_ADMIN_GROUPS / OMNIA_AUTH_ROLE_EDITOR_GROUPSdashboard.auth.roleMapping.adminGroups / .editorGroups (lists)
OMNIA_BASE_URLdashboard.baseUrl, else derived from dashboard.ingress.host and dashboard.ingress.tls
OMNIA_AUTH_PROXY_HEADER_USER / _EMAIL / _GROUPS / _DISPLAY_NAMEdashboard.proxy.headerUser / .headerEmail / .headerGroups / .headerDisplayName
OMNIA_OAUTH_PROVIDER, _ISSUER_URL, _SCOPESdashboard.oauth.provider, .issuerUrl, .scopes
OMNIA_OAUTH_AZURE_TENANT_ID / OMNIA_OAUTH_OKTA_DOMAINdashboard.oauth.azureTenantId / .oktaDomain
OMNIA_OAUTH_CLAIM_USERNAME / _EMAIL / _DISPLAY_NAME / _GROUPSdashboard.oauth.claims.username / .email / .displayName / .groups
OMNIA_BUILTIN_ALLOW_SIGNUP, _VERIFY_EMAIL, _MIN_PASSWORD_LENGTH, _MAX_FAILED_ATTEMPTS, _LOCKOUT_DURATIONdashboard.builtin.allowSignup, .verifyEmail, .minPasswordLength, .maxFailedAttempts, .lockoutDuration
OMNIA_SESSION_REDIS_URLdashboard.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 / _EMAILdashboard.builtin.admin.username / .email
OMNIA_SMTP_HOST, _PORT, _SECURE, _FROM, _USERdashboard.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_EXPIRATIONdashboard.apiKeys.enabled, .maxPerUser, .defaultExpiration

Secret-backed values:

ValueSecret keyVariable
dashboard.auth.existingSessionSecretOMNIA_SESSION_SECRETOMNIA_SESSION_SECRET. Left empty, the chart generates a secret on first install and keeps it across upgrades. Not set in anonymous mode.
dashboard.oauth.existingSecretOMNIA_OAUTH_CLIENT_ID, OMNIA_OAUTH_CLIENT_SECRETthe same names
managementApi.database.existingSecretPOSTGRES_CONNPOSTGRES_CONN, on management-api. Required in builtin mode: the chart refuses to render without it
dashboard.builtin.existingPostgresSecretOMNIA_BUILTIN_POSTGRES_URLOMNIA_BUILTIN_POSTGRES_URL
dashboard.builtin.admin.existingSecretOMNIA_BUILTIN_ADMIN_PASSWORDOMNIA_BUILTIN_ADMIN_PASSWORD, on management-api
dashboard.builtin.smtp.existingSecretOMNIA_SMTP_PASSOMNIA_SMTP_PASS, on management-api

Removed and deprecated values:

  • dashboard.builtin.storeType and dashboard.builtin.sqlitePath are 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.persistence is deprecated, and nothing mounts it. It was the dashboard’s SQLite volume. For one release the chart still renders <release>-dashboard-data, when enabled is true and existingClaim is empty, annotated helm.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.

The OMNIA_OAUTH_PROVIDER values are generic, google, azure, okta and cognito. Every provider discovers its configuration via <issuer>/.well-known/openid-configuration.

For any OpenID Connect compliant provider.

Terminal window
OMNIA_OAUTH_PROVIDER=generic
OMNIA_OAUTH_ISSUER_URL=https://auth.example.com

Required: OMNIA_OAUTH_ISSUER_URL

Scopes: openid profile email groups

Terminal window
OMNIA_OAUTH_PROVIDER=google

Issuer: 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

Terminal window
OMNIA_OAUTH_PROVIDER=azure
OMNIA_OAUTH_AZURE_TENANT_ID=your-tenant-id

Issuer: https://login.microsoftonline.com/{tenant}/v2.0

Scopes: openid profile email

Console: Azure Portal

Group claims: added under Token Configuration > Add groups claim

Terminal window
OMNIA_OAUTH_PROVIDER=okta
OMNIA_OAUTH_OKTA_DOMAIN=your-domain.okta.com

Issuer: 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

Terminal window
OMNIA_OAUTH_PROVIDER=cognito
OMNIA_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

GET /api/auth/login?returnTo=/agents

Initiates 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 /.

GET /api/auth/callback?code=xxx&state=xxx

OAuth callback endpoint. Handles authorization code exchange.

POST /api/auth/logout

Clears session. In OAuth mode, may redirect to IdP for SSO logout.

Response:

{
"success": true,
"redirectUrl": "https://idp.example.com/logout?..." // Optional
}
GET /api/auth/session

Returns whether the request is authenticated. It does not return user details.

Response:

{ "authenticated": true }
GET /api/settings/api-keys
POST /api/settings/api-keys
DELETE /api/settings/api-keys/:id

Manage API keys for the current user.

POST /api/auth/builtin/login

Authenticate 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.

POST /api/auth/builtin/signup

Register 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").

POST /api/auth/builtin/forgot-password

Request a password reset token.

Request:

{
"email": "jdoe@example.com"
}

Response: Always returns success (prevents email enumeration).

POST /api/auth/builtin/reset-password

Set new password using reset token.

Request:

{
"token": "reset-token-from-email",
"password": "newpassword123"
}

Returns an error message string on failure.

POST /api/auth/builtin/verify-email

Verify email address using verification token.

Request:

{
"token": "verification-token-from-email"
}

Returns an error message string on failure.

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;
}

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):

ActionViewerEditorAdmin
Manage own API keysYesYesYes
View all users’ API keysNoNoYes
View and edit dashboard settingsNoNoYes
Manage usersNoNoYes
List credentials across every workspaceNoNoYes
Manage credentials in a namespace no workspace ownsNoNoYes
Manage access bindings on any workspaceNoNoYes

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):

ActionViewerEditorOwner
View agents, prompt packs, tools, sessions, logs, metricsYesYesYes
View the workspace’s providersYesYesYes
View the workspace’s credentials (metadata only)NoYesYes
Deploy, scale and delete agentsNoYesYes
Modify prompt packs and toolsNoYesYes
Create, update and delete the workspace’s credentialsNoNoYes
Change a provider’s credential (secretRef)NoNoYes
Manage workspace membersNoNoYes

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.

  1. Check admin groups
  2. Check editor groups
  3. Default to viewer

First match wins. Example:

Terminal window
OMNIA_AUTH_ROLE_ADMIN_GROUPS=admins,super-users
OMNIA_AUTH_ROLE_EDITOR_GROUPS=developers,ops

User with groups ["developers", "admins"] gets admin role (checked first).

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.

HeaderDescription
AuthorizationBearer token: Bearer omnia_sk_...
X-API-KeyAlternative API key header
CookieSession cookie (browser requests)
ErrorDescription
invalid_stateCSRF state mismatch
no_codeNo authorization code in callback
callback_failedToken exchange failed
access_deniedUser denied consent
invalid_claimsMissing required claims
config_errorOAuth misconfiguration
StatusErrorSourceDescription
401unauthenticatedmanagement-api sign-in gateNo valid session or API key, or a credential that does not check out
401unauthorizedmanagement-api caller resolutionThe caller could not be resolved, for example with the session store unreachable
403Forbiddenmanagement-api operation guardInsufficient permissions

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.

HeaderValue
Strict-Transport-Securitymax-age=63072000; includeSubDomains; preload
Content-Security-Policydefault-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-OptionsDENY
X-Content-Type-Optionsnosniff
Referrer-Policystrict-origin-when-cross-origin
Permissions-Policycamera=(), 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:

HeaderValue
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENY
Strict-Transport-Securitymax-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.

management-api uses these URLs for the IdP redirect:

ProviderRedirect URI
Allhttps://your-domain/api/auth/callback

Post-logout redirect:

ProviderPost-Logout URI
Allhttps://your-domain/login