Gateway

Gateway protocol auth

How a client proves who it is: the handshake auth paths, device identity and pairing signatures, and TLS pinning.

Auth

  • Shared-secret gateway auth uses connect.params.auth.token or connect.params.auth.password, depending on the configured gateway.auth.mode ("none" | "token" | "password" | "trusted-proxy").
  • Identity-bearing modes such as Tailscale Serve (gateway.auth.allowTailscale: true) or non-loopback gateway.auth.mode: "trusted-proxy" satisfy the connect auth check from request headers instead of connect.params.auth.*.
  • Private-ingress gateway.auth.mode: "none" skips shared-secret connect auth entirely; do not expose that mode on public/untrusted ingress.
  • After pairing, the gateway issues a device token scoped to the connection role + approved grant, returned in hello-ok.auth.deviceToken. Clients should persist it with hello-ok.auth.scopes after a successful connect when the token is new or different from the stored token.
  • hello-ok.auth.scopes is the current socket's live authority and matches the scopes enforced by RPC dispatch.
  • When hello-ok.auth.deviceToken exactly matches the token already stored for the same gateway, device, client, and role, preserve that record's stored scopes instead of replacing them with a narrower live scope set. A newly issued or rotated token uses hello-ok.auth.scopes; its approved grant matches that connection when it is issued.
  • Reconnecting with that stored device token should also reuse the stored approved scope set for that token. This preserves read/probe/status access already granted and avoids silently collapsing reconnects to a narrower implicit admin-only scope.
  • Client-side connect auth assembly (selectConnectAuth in packages/gateway-client/src/client.ts):
    • auth.password is orthogonal and always forwarded when set.
    • auth.token is populated in priority order: explicit shared token first, then an explicit deviceToken, then a stored per-device token (keyed by deviceId + role).
    • auth.bootstrapToken is sent only when none of the above resolved auth.token. A shared token or any resolved device token suppresses it.
    • Auto-promotion of a stored device token on the one-shot AUTH_TOKEN_MISMATCH retry is gated to trusted endpoints only: loopback, or wss:// with a pinned tlsFingerprint. Public wss:// without pinning does not qualify.
  • Built-in setup-code bootstrap returns the primary node hello-ok.auth.deviceToken plus a bounded operator token in hello-ok.auth.deviceTokens for trusted mobile handoff. The operator token includes operator.talk.secrets for native Talk configuration reads, but excludes pairing-mutation scopes and operator.admin.
  • hello-ok.auth.deviceTokens contains only additional bootstrap-handoff tokens. Do not use it as metadata for the primary deviceToken reconnect record.
  • While a non-baseline setup-code bootstrap waits for approval, PAIRING_REQUIRED details include recommendedNextStep: "wait_then_retry", retryable: true, and pauseReconnect: false. Keep reconnecting with the same bootstrap token until the request is approved or the token becomes invalid.
  • Persist hello-ok.auth.deviceTokens only when the connect used bootstrap auth on a trusted transport such as wss:// or loopback/local pairing.
  • If a client supplies an explicit deviceToken or explicit scopes, that caller-requested scope set remains authoritative for the live connection and is reported in hello-ok.auth.scopes; cached token-grant scopes are only reused when the client is reusing the stored per-device token.
  • Device tokens can be rotated/revoked via device.token.rotate and device.token.revoke (requires operator.pairing). Rotating or revoking a node or other non-operator role also requires operator.admin.
  • device.token.rotate returns rotation metadata. It echoes the replacement bearer token only for same-device calls already authenticated with that device token, so token-only clients can persist their replacement before reconnecting. Shared/admin rotations do not echo the bearer token.
  • Token issuance, rotation, and revocation stay bounded to the approved role set recorded in that device's pairing entry; token mutation cannot expand or target a device role that pairing approval never granted.
  • For paired-device token sessions, device management is self-scoped unless the caller also has operator.admin: non-admin callers can manage only the operator token for their own device entry. Node and other non-operator token management is admin-only, even for the caller's own device.
  • device.token.rotate and device.token.revoke also check the target operator token scope set against the caller's current session scopes. Non-admin callers cannot rotate or revoke a broader operator token than they already hold.
  • Auth failures include error.details.code plus recovery hints:
    • error.details.canRetryWithDeviceToken (boolean)
    • error.details.recommendedNextStep: one of retry_with_device_token, update_auth_configuration, update_auth_credentials, wait_then_retry, review_auth_configuration (packages/gateway-protocol/src/connect-error-details.ts).
  • Client behavior for AUTH_TOKEN_MISMATCH:
    • Trusted clients may attempt one bounded retry with a cached per-device token.
    • If that retry fails, stop automatic reconnect loops and surface operator action guidance.
  • AUTH_SCOPE_MISMATCH means the device token was recognized but does not cover the requested role/scopes. Do not present this as a bad token; prompt the operator to re-pair or approve the narrower/broader scope contract.

Device identity and pairing

  • Nodes should include a stable device identity (device.id) derived from a keypair fingerprint.
  • Gateways issue tokens per device + role.
  • Pairing approvals are required for new device IDs unless local auto-approval is enabled.
  • Pairing auto-approval is centered on direct local loopback connects.
  • OpenClaw also has a narrow backend/container-local self-connect path for trusted shared-secret helper flows.
  • Same-host tailnet or LAN connects are still treated as remote for pairing and require approval.
  • WS clients normally include device identity during connect (operator + node). The only device-less operator exceptions are explicit trust paths:
    • successful gateway.auth.mode: "trusted-proxy" operator Control UI auth.
    • direct-loopback gateway-client backend RPCs on the reserved internal helper path.
  • Omitting device identity has scope consequences. When a device-less operator connection is allowed through an explicit trust path, OpenClaw still clears self-declared scopes to an empty set unless that path has a named scope-preservation exception. Scope-gated methods then fail with missing scope.
  • The reserved direct-loopback gateway-client backend helper path preserves scopes only for internal local control-plane RPCs; custom backend IDs do not receive this exception.
  • All connections must sign the server-provided connect.challenge nonce.

Device auth migration diagnostics

For legacy clients that still use pre-challenge signing behavior, connect returns DEVICE_AUTH_* detail codes under error.details.code with a stable error.details.reason.

Common migration failures:

Message details.code details.reason Meaning
device nonce required DEVICE_AUTH_NONCE_REQUIRED device-nonce-missing Client omitted device.nonce (or sent blank).
device nonce mismatch DEVICE_AUTH_NONCE_MISMATCH device-nonce-mismatch Client signed with a stale/wrong nonce.
device signature invalid DEVICE_AUTH_SIGNATURE_INVALID device-signature Signature payload does not match v2 payload.
device signature expired DEVICE_AUTH_SIGNATURE_EXPIRED device-signature-stale Signed timestamp is outside allowed skew.
device identity mismatch DEVICE_AUTH_DEVICE_ID_MISMATCH device-id-mismatch device.id does not match public key fingerprint.
device public key invalid DEVICE_AUTH_PUBLIC_KEY_INVALID device-public-key Public key format/canonicalization failed.

Migration target:

  • Always wait for connect.challenge.
  • Use connect.challenge.payload.ts as connect.params.device.signedAt.
  • Sign the v2 payload that includes the server nonce.
  • Send the same nonce in connect.params.device.nonce.
  • Preferred signature payload is v3 (buildDeviceAuthPayloadV3 in packages/gateway-client/src/device-auth.ts), which binds platform and deviceFamily in addition to device/client/role/scopes/token/nonce fields.
  • Legacy v2 signatures remain accepted for compatibility, but paired-device metadata pinning still controls command policy on reconnect.

TLS and pinning

  • TLS is supported for WS connections (gateway.tls config).
  • Clients may optionally pin the gateway cert fingerprint via gateway.remote.tlsFingerprint or CLI --tls-fingerprint.
Was this useful?
On this page

On this page