Auth & SSO

Omnigent supports three ways to authenticate users. Pick the one that fits your setup:

ModeWhen to use
Built-in accountsStandalone deploy, no external IdP. Username/password with invite links.
Single sign-on (OIDC)Your own IdP: Google, GitHub, Okta, Microsoft.
Header-based authBehind an existing SSO proxy that injects X-Forwarded-Email.

Built-in accounts

If you deployed with Docker or a cloud platform, auth is already enabled by default with built-in accounts.

Non-Docker deploys

For non-Docker deploys, enable built-in accounts manually:

OMNIGENT_AUTH_ENABLED=1 omni server start
  1. Create the first admin.
    • For better security, the server never auto-generates a password. When no admin is configured, the server will report needs_setup.
    • To configure, open the web UI and create an admin account or run omni server in a terminal and answer the username and password prompt.
    • For headless deploys, preset the password with --admin-password or OMNIGENT_ACCOUNTS_INIT_ADMIN_PASSWORD.
  2. Invite teammates.
    • Go to Admin > Members > Invite to create a single-use invite link.
    • No email server needed; just send the link directly. Signup is invite-only.

Security note for public deployments. While no password-bearing account exists, the Create-admin endpoint (POST /auth/setup) is intentionally unauthenticated, so an instance exposed on a public URL before its operator reaches the form can be claimed by the first visitor. Pre-seed OMNIGENT_ACCOUNTS_INIT_ADMIN_PASSWORD so the admin is created directly at boot, or keep the service private until you've completed setup.

Single sign-on (OIDC)

Let your team sign in with Google, GitHub, Okta, or Microsoft. Adding an OIDC issuer flips the mode to SSO. No extra flag needed.

Set the following in deploy/docker/.env:

OMNIGENT_OIDC_ISSUER=https://accounts.google.com
OMNIGENT_DOMAIN=agents.yourcompany.com
OMNIGENT_OIDC_CLIENT_ID=...
OMNIGENT_OIDC_CLIENT_SECRET=...
docker compose up -d    # restart to apply

The only outside step is creating an app with your provider (e.g. Google Cloud Console, or GitHub > Settings > Developer settings) to get the client ID and secret. Set its callback URL to https://<your-domain>/auth/callback.

Custom email claim

Omnigent reads the user's identity from the email claim of the IdP's id_token by default. Some providers omit that claim — Microsoft Entra ID commonly issues only preferred_username (the UPN) — which fails login with Could not determine user email. Point Omnigent at the claim that carries the identity instead:

OMNIGENT_OIDC_EMAIL_CLAIM=preferred_username
OMNIGENT_OIDC_SKIP_EMAIL_VERIFICATION=1

email_verified describes the email claim only, so a custom claim carries no verified marker. Logins are rejected unless you also set OMNIGENT_OIDC_SKIP_EMAIL_VERIFICATION=1 to waive that gate — only enable this for an IdP you trust to vouch for the claim. When set, the claim value is trimmed and normalized like an email address. This applies to the generic-OIDC path only; GitHub always requires a verified primary email.

Public PKCE clients and explicit endpoints

The generic-OIDC path (Okta, Auth0, Keycloak, Entra ID, and any other IdP that publishes /.well-known/openid-configuration) accepts two further operator-level options. Both configure Omnigent login only — not upstream MCP credentials or shared-session credential ownership — and neither relaxes the signature, expiration, email-verification, admission, or reauthentication checks that run on every sign-in.

Public clients (no client secret)

If you registered Omnigent with your IdP as a public client, opt in with OMNIGENT_OIDC_TOKEN_ENDPOINT_AUTH_METHOD=none and leave OMNIGENT_OIDC_CLIENT_SECRET unset:

OMNIGENT_OIDC_ISSUER=https://identity.example.com
OMNIGENT_OIDC_CLIENT_ID=omnigent
OMNIGENT_OIDC_TOKEN_ENDPOINT_AUTH_METHOD=none

Omnigent still sends its client ID, redirect URI, and PKCE verifier to the token endpoint, but omits the client_secret field entirely. PKCE S256 is used in both modes, so the IdP must enforce PKCE and one-time authorization codes.

This is an explicit opt-in. The default, client_secret_post, continues to require OMNIGENT_OIDC_CLIENT_SECRET — a missing secret never silently switches you to public-client mode. The server refuses to start if you set none together with a client secret, if you set any other value, or if you combine none with GitHub login (GitHub always needs the secret). If you are migrating from a custom integration that inferred public-client mode from an absent secret, add OMNIGENT_OIDC_TOKEN_ENDPOINT_AUTH_METHOD=none explicitly.

Explicit endpoints (skip discovery)

When discovery is unavailable, or advertises endpoints that don't suit your deployment, set all three overrides and the server skips the discovery fetch at startup:

OMNIGENT_OIDC_ISSUER=https://identity.example.com
OMNIGENT_OIDC_CLIENT_ID=omnigent
OMNIGENT_OIDC_TOKEN_ENDPOINT_AUTH_METHOD=none
OMNIGENT_OIDC_AUTHORIZATION_ENDPOINT=https://login.example.com/authorize
OMNIGENT_OIDC_TOKEN_ENDPOINT=https://tokens.example.com/token
OMNIGENT_OIDC_JWKS_URI=https://keys.example.com/jwks
OMNIGENT_OIDC_REDIRECT_URI=https://omnigent.example.com/auth/callback
OMNIGENT_OIDC_COOKIE_SECRET=<64-hex-chars>

Only trusted operators should set these addresses. The token endpoint receives authorization codes and, for confidential clients, the client secret; the JWKS endpoint decides which signing keys are trusted. Each override must be an absolute HTTPS URL with no embedded credentials or fragment. Plain HTTP is accepted only for localhost, 127.0.0.1, or [::1] for local testing.

Signing algorithms

ID tokens signed with PS256 are verified alongside the existing RSA (RS256/RS384/RS512) and EC (ES256/ES384/ES512) algorithms.

Verifying the setup

Restart the server with the new settings, sign in from a private browser window, then run omni login <server-url> and complete the browser flow. Both should identify the same expected account.

If your enterprise directory omits email_verified, the separate OMNIGENT_OIDC_SKIP_EMAIL_VERIFICATION=1 setting (see Custom email claim) remains an explicit operator choice — the options above never enable it for you.

Header-based auth

If your server sits behind an existing SSO proxy (e.g. OAuth2 Proxy, Cloudflare Access) that injects a trusted header, Omnigent can read the user identity directly from X-Forwarded-Email. No additional auth configuration is needed on the Omnigent side.

Access control

Once auth is enabled, control who can sign in and what they can access.

Scope depends on the auth mode. allowed_domains applies to OIDC sign-ins only; leaving it empty means no domain restriction. Built-in accounts are invite-only, so domain allowlists do not apply there, and header-based auth delegates identity entirely to your proxy. The admins list works in both built-in accounts and OIDC modes.

Admins and the management surface

Admins are the identities in the admins list (above), plus anyone you add to the runtime-editable <data_dir>/admins file (reloaded without a restart). Listed identities are promoted to admin on their next sign-in.

Admins get three management sections in the web UI under Settings, in both built-in accounts and OIDC modes:

In built-in accounts mode, Members also carries the management actions (Invite member, per-user Reset password, and Remove); under OIDC it is a read-only roster, since identities are owned by your IdP and provisioned on first sign-in. Separately, every signed-in account (built-in or OIDC) gets a Settings > Account section showing who they're signed in as, with a sign-out control and, in built-in accounts mode only, Change password. Both Reset and Change password sign the account out everywhere else — see Changing or resetting a password.

Removing a member

In built-in accounts mode, Remove (DELETE /auth/users/{id}) revokes everything the account could still use to act, in one database transaction. Every authenticated request re-checks the stored account state — including on other server replicas — so there is no cache to wait out. After removal:

Removal is a database commit boundary, not a kill switch: work that was already admitted before the removal committed may finish, but nothing new can be granted to that identity afterward. You cannot remove your own account, and the last remaining admin cannot be removed.

Usernames are reusable. If you later invite the same username again, it becomes a brand-new account: it does not inherit the old account's shared sessions, connections, projects, or session ownership, and the old cookies still fail.

Provisioning a password for a shared-with user

Sharing a session with a username that has never registered creates a passwordless entry for that person. To let them sign in, use Reset on that row in Settings > Members: the first reset provisions their credentials while preserving the sessions already shared with them, and they can log in with the returned password. A later reset rotates the password and signs them out everywhere.

Upgrading an existing accounts deployment

The release that introduces this revocation model adds a schema migration that stamps existing accounts and their saved credentials. Accounts-mode deployments must upgrade every server and scheduler that shares the database together — old code does not understand the new columns, so mixed versions are unsafe.

  1. Back up the database.
  2. Stop all writers: every server and scheduler sharing the database.
  3. Upgrade the application and run the migration.
  4. Restart only the new version.
  5. Sign in again in ordinary browser sessions, and restart any provider OAuth connection handshake that began before the upgrade.

Existing refresh grants (CLI, mobile, desktop sign-ins) keep working and renew into upgraded tokens; ordinary cookies and JWTs require a fresh login. Do not roll back to the old version against the upgraded schema.

Changing or resetting a password

In built-in accounts mode, a signed-in session lasts OMNIGENT_ACCOUNTS_SESSION_TTL_HOURS (default 8). To end one user's sessions sooner, change the password (Settings > Account > Change password, POST /auth/users/me/password) or have an admin reset it (Settings > Members > Reset, POST /auth/users/{id}/reset). Either one invalidates everything issued to that user so far: browser sessions, CLI logins (omni login), refresh tokens, and unused magic sign-in links. A device sign-in approved before the change (a mobile, desktop, or Slack connection, for example) can no longer be completed afterwards. As with removal, every authenticated request re-checks the stored account state on every server replica, so there is no cache to wait out. Two exceptions keep the caller from locking themselves out:

Unlike removal, the user's scheduled tasks and host registrations are kept — but that does not mean they keep running. A host that is already connected keeps its connection and still looks online, yet launches on it fail, including scheduled runs, until it reconnects:

CLIs need omni login again too.

Servers running a release without this behaviour keep the user's sessions when they change or reset a password. During a rolling upgrade, repeat a change made while such instances were still serving.

Signing everyone out

Rotating OMNIGENT_ACCOUNTS_COOKIE_SECRET signs out every user at once: it invalidates all session tokens, CLI logins, and refresh tokens. Unused sign-in and invite links are looked up by value, not signed with the secret, so they keep working until they expire — sign-in links after OMNIGENT_ACCOUNTS_MAGIC_TTL_MINUTES (default 10), invites after OMNIGENT_ACCOUNTS_INVITE_TTL_HOURS (default 72).

Session sharing

Admins set a server-wide sharing policy that governs how users can share sessions with each other. It applies to every session on the server and changes only new grants — revoke, listing existing grants, session ownership, and grants already in place keep working in every mode. Configure it two ways:

The sharing mode has four tiers:

ModeBehavior
onGrants at any level (read / edit / manage) plus public and workspace read. This is the default.
read_onlyNew grants are capped at read (view); edit and manage grants are rejected.
restricted_read_onlyRead-only, and sessions whose working directory is a user home directory or the filesystem root cannot be shared at all — not even read.
offSharing is disabled. No new grants can be created and the Share control is hidden.
OMNIGENT_SHARING_MODE=read_only omni server start

An unset or unrecognized value fails open to on. Under restricted_read_only, a workspace is considered too broad to share when its path is the filesystem root (/), root's home (/root), or a direct child of a home parent such as /home/alice, /Users/bob, or /var/home/carol; a subdirectory of a home (e.g. /home/alice/proj) stays shareable.

Public access

Separately from the tiers above, a public-access switch controls whether a session can be shared with anyone who has the link (public read access). It is independent of the sharing mode — you can keep normal user-to-user sharing on while disabling public links.

OMNIGENT_PUBLIC_SHARING=off omni server start

Public access is enabled unless the value is explicitly falsy (0 / false / no / off); unset or unrecognized fails open to enabled. It is also editable at runtime from Settings > Sharing. When disabled, the Share dialog hides its Public access toggle and the server rejects new public grants; sessions already shared publicly stay public until revoked.

Default visibility for new sessions

By default every session starts private and its owner shares it explicitly. A third setting, Default visibility for new sessions, lets an admin make new sessions start with public read access instead. It has three options:

ValueSettings > Sharing labelBehavior
offPrivateNew sessions start private. Owners share them explicitly. This is the default.
sandboxCloud sandbox sessions publicSessions running in a server-managed cloud sandbox start public; sessions on a user's own machine stay private.
allAll sessions publicEvery new session starts with public read access.
OMNIGENT_DEFAULT_PUBLIC_SESSIONS=sandbox omni server start

Unlike the two settings above, this one fails closed: an unset or unrecognized value resolves to off, so a typo never exposes sessions. It is also editable at runtime from Settings > Sharing, or via default_public_sessions on PUT /v1/sharing.

The default is applied when a session is created, forked, or fired by a scheduled task (a task whose execution target is managed_sandbox counts as a sandbox session). It is only the creation default: owners can revoke the public grant from the Share dialog at any time, and existing sessions are never changed. A few sessions are always left private:

The default never grants more than an owner could grant by hand. It is inactive while the sharing mode is off or public access is disabled, and under restricted_read_only it skips sessions whose working directory is too broad to share (see above). Settings > Sharing greys out the control and shows the effective value (Private) while a higher-level setting overrides it; the saved choice returns once the blocker is lifted.

When a deployment manages the sharing mode, public access, or default visibility itself (for example a managed service that injects its own policy), Settings > Sharing shows the current value as read-only and cannot change it here.

Domain allowlist

Restrict sign-ups to specific email domains. In your server config (/data/config.yaml):

allowed_domains: [yourcompany.com]
admins: [you@yourcompany.com]

Invite outsiders

Need to let in someone outside your domain, like a contractor? Set OMNIGENT_OIDC_ALLOW_INVITES=1 and send them a one-time invite link.

Migration

Already using one auth mode and want to switch? These commands move your existing users across without losing sessions or admin rights.

From built-in accounts to SSO

If you started with built-in accounts and want to switch to OIDC, one command brings everyone across so they keep their sessions and admin rights:

omni debug migrate-accounts-to-oidc <database-url> --domain yourcompany.com --commit

Without --commit the command is a dry run that reports what would change without modifying anything.