Auth & SSO
Omnigent supports three ways to authenticate users. Pick the one that fits your setup:
| Mode | When to use |
|---|---|
| Built-in accounts | Standalone deploy, no external IdP. Username/password with invite links. |
| Single sign-on (OIDC) | Your own IdP: Google, GitHub, Okta, Microsoft. |
| Header-based auth | Behind 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
- 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 serverin a terminal and answer the username and password prompt. - For headless deploys, preset the password with
--admin-passwordorOMNIGENT_ACCOUNTS_INIT_ADMIN_PASSWORD.
- For better security, the server never auto-generates a password. When no admin is configured, the server will report
- 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>
- Setting only one or two of the overrides fails at startup rather than mixing endpoint sources; leave all three unset to keep using discovery.
- Overrides work with confidential clients too, and public-client mode works with discovery — the two options are independent.
- Overrides are not supported for GitHub login, which keeps its fixed endpoints.
- Endpoint hosts may differ from the issuer, but the ID token's
issmust still matchOMNIGENT_OIDC_ISSUERexactly and itsaudmust match the client ID. - Query parameters on the authorization endpoint (for example
?p=policy) are preserved. Omnigent's own OAuth parameters win on name collisions.
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:
- Settings > Members — the user roster (username, role, last login).
- Settings > Policies — server-wide contextual policies.
- Settings > Sharing — the server-wide session-sharing policy (see Session sharing below).
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:
- Sign-in stops immediately. Existing browser cookies and JWTs are
rejected, and the account's device/refresh grants (
omni login, mobile and desktop sign-ins) are revoked, so token refresh fails withinvalid_grant. Outstanding invite and magic-login links created for or by the user are deleted. - Saved authority is removed. Session-sharing grants, saved provider connections (GitHub, Databricks), projects, and daily spending/budget checkpoints are deleted. Sessions that lived in the removed user's projects are detached from the project, not deleted.
- Scheduled tasks are disabled. The user's scheduled tasks are marked deleted and never fire again.
- Hosts are removed. Hosts the user registered are torn down as if deleted from the host list: bound sessions are detached and launch credentials cleared. A managed cloud-sandbox host is kept as a tombstone until its sandbox is confirmed terminated.
- Session history is retained. Conversations stay in the database, but no new runner can be launched for them on the removed owner's behalf.
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.
- Back up the database.
- Stop all writers: every server and scheduler sharing the database.
- Upgrade the application and run the migration.
- Restart only the new version.
- 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:
- A self-serve password change keeps the browser that made it signed in. A caller that changed the password with a bearer token must log in again.
- An admin resetting their own password keeps the session that performed the reset, so they can save the returned password before signing in again.
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:
- A host signed in with the user's credentials (
omni hostafteromni login) cannot reconnect with them. Runomni loginagain, then restart the host. - A managed cloud sandbox host, which the server starts in a sandbox, connects with its own launch token rather than the user's credentials. It needs no login: once it reconnects with that token, before the token expires, launches work again.
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:
- At boot, with the
OMNIGENT_SHARING_MODEenvironment variable. - At runtime, from Settings > Sharing in the web UI. This persists an override that takes effect immediately, without a restart.
The sharing mode has four tiers:
| Mode | Behavior |
|---|---|
on | Grants at any level (read / edit / manage) plus public and workspace read. This is the default. |
read_only | New grants are capped at read (view); edit and manage grants are rejected. |
restricted_read_only | Read-only, and sessions whose working directory is a user home directory or the filesystem root cannot be shared at all — not even read. |
off | Sharing 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:
| Value | Settings > Sharing label | Behavior |
|---|---|---|
off | Private | New sessions start private. Owners share them explicitly. This is the default. |
sandbox | Cloud sandbox sessions public | Sessions running in a server-managed cloud sandbox start public; sessions on a user's own machine stay private. |
all | All sessions public | Every 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:
- Sub-agent sessions, which follow their parent's grants instead.
- Side chats, which are private scratch forks of the caller's own view.
- Single-user (no-auth) servers, where there is no sharing at all.
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.