OpenClaw EnterpriseDOCSGitHub

External sign-in and account controls

Google and generic OIDC sign-in for existing OpenClaw Control Plane (OCC) accounts share these session, recovery and account controls. The authentication reference covers bootstrap, password sessions, request origin, provisioning and failures.

Console sign-in destinations

Sign-in restores the validated page, Namespace, and Agent tab or version after expiry, then rechecks access. It clears private views and drafts and never replays writes. Logout or failed external sign-in clears the destination. Agent recovery ends at its console page; opening native UI remains a separate action.

External sign-in profile

Google and generic OIDC sign-in require one serving controller, one Installation, PostgreSQL with its restricted application role, native IAM, a canonical HTTPS Console origin, and an Agent DNS suffix for shared browser sessions. Rolling or mixed-version serving and external identity or policy writers are unsupported. Keep bootstrap, seeding and recovery-affecting changes stopped during activation. Loopback development does not qualify deployed HTTPS.

OIDC supports native Agent access. Google requires native administration to be disabled. Both use the generic OCC_AUTH_RECOVERY_USER_ID setting, rendered from Helm auth.recoveryUserId, to seed the first local recovery administrator. Activation enrolls qualifying existing accounts and reports accounts that cannot sign in. Account creation remains available after activation.

A human administrator explicitly attaches the provider's immutable subject to an existing OCE account. Attachment keeps its user ID, Principal and grants, advances its account version, and invalidates sessions and pending proofs. An identity already held by another account returns 409 RESOURCE_CONFLICT. Email association, identity transfer and self-service linking are unsupported. Opt-in OIDC enrollment can create unknown identities and assign initial access; it never links them by email.

Changing a client ID, or the OIDC issuer, creates a new provider instance and ends old-instance sessions on their next request. Attach identities under the intended instance explicitly. Client-secret rotation invalidates pending attempts and preserves attachments. Direct GitHub browser sign-in is retired; follow the upgrade procedure before installing this release. Repository GitHub credentials remain separate.

Browser callback and audit

GET /api/auth/providers returns google, oidc, password and sessionBinding. When OIDC is configured, oidcSignIn supplies its label and authorizationUrl. A same-origin POST /api/auth/providers/oidc/start returns data.url and a public data.attemptId, and sets a browser-binding cookie. Google uses the matching google routes. Callers cannot select callback or return destinations.

The callback consumes a five-minute, browser-bound attempt before exchanging its code. Signature, issuer, audience, expiry and nonce checks precede identity lookup. Success returns to /console/ with a two-minute HttpOnly, SameSite=Strict receipt. The starting tab posts attemptId to the provider's /result route and receives the callback session's sessionKey once, only while that session cookie is current. The exchange does not issue or extend a session.

Failure returns to /console/?authError=oidc (or google) without automatic retry. An authenticated identity attached to a disabled account gets authReason=account-disabled, audited as ACCOUNT_DISABLED. Provider unavailability gets authReason=provider-unavailable, audited as PROVIDER_UNAVAILABLE. Other identity and admission refusals remain generic. An internal dependency failure or uncertain commit gets authReason=unavailable; it is not an identity denial and is not retried automatically.

Malformed, replayed, expired or unbound callbacks prove nothing about their sender. They increment occ_sign_in_unmatched_callbacks_total instead of writing audit records. Matched attempts record denials. Successful external login audits include the verified subject and attempt ID.

Provider failures log authentication.provider-unavailable-warning with the provider, instance, step (authorization, token or jwks), and a bounded cause. Causes include network errors, timeout, redirect, HTTP failure, oversized or malformed response, provider error, and rejected client configuration. Logs omit tokens, client secrets and response bodies. Check the endpoint, client credentials, TLS trust and API egress. Provider bodies share a 64 KiB limit and requests share a 10-second deadline.

Recovery-only password sign-in

By default every enrolled account can still sign in with its password once a provider is enabled, so strangers who know an email can spend that account's password sign-in budget. OCC_AUTH_PASSWORD_SIGN_IN=recovery-only (Helm auth.passwordSignIn: recovery-only; default all) removes that surface: only the recovery account signs in with a password, and every other account uses its attached Google or OIDC identity. It requires a configured provider; startup and Helm refuse it otherwise, and any other value.

Every other email, existing or not, receives the ordinary 401 bad-credential answer after the same password hashing, audited as INVALID_CREDENTIALS; no account is read, so the answer reveals nothing. GET /api/auth/providers reports password: false. The Console keeps the password form behind Recovery sign-in while discovering sign-in methods, then adds the configured provider buttons. During a provider outage only the recovery account can sign in.

An account without an identity for a configured provider cannot sign in until an administrator attaches one. Each startup with recovery-only logs authentication.password-sign-in-warning (EXTERNAL_IDENTITY_MISSING) with the enabled accounts, other than the recovery account, that lack one, in the skippedUserIds fields that the activation warning also uses. To switch:

  1. Keep all. As an Installation administrator, attach an identity to every ordinary account (attachment), and have each person sign in with it once.
  2. Set recovery-only and upgrade. If the warning lists accounts, attach their identities; that takes effect without a restart. Setting all again and upgrading restores passwords.

The setting gates new password sign-ins only. Password sessions that already exist keep working until they expire (at most 8 hours): those of ordinary accounts after the switch to recovery-only, and the former holder's after POST /api/auth/recovery moves the designation. To end them at once, call POST /api/auth/accounts/:userId/revoke for each account, which ends all of its sessions, or run purge-sessions with stopped maintenance.

Attach an identity explicitly to a manually created account, or use OIDC enrollment for a new identity.

Session and recovery controls

Password, Google and OIDC sessions share admission rules: an eight-hour lifetime without refresh, current account and method checks, and required audit before a cookie is released or, on logout, cleared. Older sessions without account/method binding are rejected; users sign in again. An external session authenticates only while its provider instance is configured: removing a provider, or changing its client ID (or OIDC issuer), ends that instance's sessions on their next request, audited once as authentication.session.end with reason PROVIDER_NOT_CONFIGURED. Password sessions are unaffected. Activation is one-way: removing every provider fails startup, and the database refuses sessions from older binaries. Returning to password-only sign-in needs stopped maintenance.

The recovery user needs one local password, its Installation Principal, and native IAM Installation administer; disabling it returns 409. Keep its password in protected custody; out-of-band database or policy changes can remove it. Password login never depends on an external provider.

POST /api/auth/recovery (userId, expectedCurrentUserId, target expectedVersion) moves the designation (GET reads it) to another qualifying user. The caller needs every IAM grant of the current holder's Principal (else 403). The variable, like auth:maintain activate --recovery-user, then only seeds first activation; a differing value warns, and each start re-checks the holder.

Account reads and mutations require a human session, exact Origin, and Installation administer; service keys are refused. Account mutations also require every IAM grant of the target account's Principal (else 403, audited as ACCOUNT_PRINCIPAL_GRANTS_NOT_COVERED with the target userId and principalId, the current holder's for a recovery move); Agent administer counts for a delegated Agent read_logs grant, as for service keys. After locking, State rechecks caller authority, target coverage and the recovery successor, then the session. Policy stays stable through commit. Lock contention returns retryable 503; lost caller authority or target coverage returns 403 without mutation. Stale expectedVersion, disabled targets or successors lacking Installation administer return 409 RESOURCE_CONFLICT.

Send the version just read, such as {"expectedVersion":1}:

Operation Effect
POST /api/auth/accounts/:userId/disable Disables the account, invalidating sessions and pending proofs; refuses the recovery user.
POST /api/auth/accounts/:userId/enable Re-enables a disabled account; users sign in again.
POST /api/auth/accounts/:userId/revoke Invalidates all account sessions and pending proofs; fresh sign-in still works.
POST /api/auth/accounts/:userId/methods/:methodId/detach Removes one attached external identity and ends every account session; password methods return 409.

POST /api/auth/accounts/:userId/enrol (no body) enrolls a skipped account holding its Principal and one password. These operations serialize with session issuance and leave IAM grants unchanged. An unknown administrative COMMIT returns 503 DEPENDENCY_UNAVAILABLE with an unknown-outcome message, never success, automatic replay, or compensation. An account read shows present state, not a receipt: the original transaction may still be running. Resolve uncertainty before choosing a new action and version. Password reset and deletion remain deferred.

These account and recovery routes need Google or OIDC sign-in. In the password-only profile an authorized administrator receives 409 RESOURCE_CONFLICT naming that requirement; the profile has no account version, disabled state, or session binding, so it cannot disable an account or revoke its sessions online. Enable an external provider to use these controls.

Password sign-in has the password-only profile's failure-counting limit: only failed sign-ins spend it, and a spent email is slowed and answered with 429 and Retry-After. The recovery account and Installation administrators are slowed, never refused: their correct password still signs in. A browser with a valid known-device cookie for the email spends its own budget instead. Google and OIDC start, callback, and result each allow 30 requests/minute and four active per client address, eight active in all; a sign-in spends one of each. Without trusted proxies every browser behind an ingress shares its address, so the address is never a key: callback and result key on the browser's attempt and receipt cookies, and start is limited only by the active request cap and the 1,000 pending attempts (oldest evicted). Startup and Helm's install notes warn (trusted proxies). A 4,096-key table bounds memory. Provider calls share a ten-second deadline, refuse redirects, read at most 64 KiB. Limits are per controller.

Search documentation