#How do I use token and password auth?

1 messages · Page 1 of 1 (latest)

molten night
#

I need password auth due to the fact that I'm setting up a local node on a Galaxy Watch6 (personally developed) and it'd be both hard and annoying to enter in a gateway token everytime.

red canyonBOT
#

Yep — you can use password auth for the Gateway, but one important nuance first:

  • In the Control UI, tokens are persisted in the UI settings, but passwords are not persisted (so password mode can actually be more annoying there). Source: https://docs.openclaw.ai/web/control-ui

That said, for a custom Galaxy Watch client/node (your own app), password auth can be nicer because you can pick something shorter/easier to type once, and then store it.

1) Configure the Gateway for password auth

On the machine running the Gateway:

Option A (recommended): set password via env var

Put it in ~/.openclaw/.env so it works with daemon/service setups:

cat >> ~/.openclaw/.env <<'EOF'
OPENCLAW_GATEWAY_PASSWORD=your-strong-password
EOF
#

openclaw config set gateway.auth.mode password
openclaw gateway restart

Docs: https://docs.openclaw.ai/gateway/security (see “Auth modes”)

Option B: store it in config (works, but less ideal: secret on disk)

openclaw config set gateway.auth.mode password
openclaw config set gateway.auth.password "your-strong-password"
openclaw gateway restart

2) What your watch client should send

For the WebSocket handshake, your client can send:

#
  • connect.params.auth.password (initial bootstrap), or
  • connect.params.auth.deviceToken (after first successful connect/pair)

Protocol schema shows all three auth fields: token, password, deviceToken. Source: https://docs.openclaw.ai/gateway/protocol

3) The real “don’t type it every time” solution: persist the device token

After your first successful connect, the Gateway returns:

  • hello-ok.auth.deviceToken

Persist that in the watch app (secure storage), and on future connects use:

  • connect.params.auth.deviceToken

That way you only ever type the shared password/token once (or even never, if you provision it during development).


#

If you tell me which one you’re building:

  1. a custom WS client that talks directly to the Gateway, or
  2. running openclaw itself on the watch in some “remote mode” setup,
    …I can point you at the exact config surface you should use.
molten night
red canyonBOT
#

A few gotchas here—<public-ip>:18789 failing is usually one of these:

0) First: what “doesn’t work” mean?

  • Browser shows “site can’t be reached” / timeout / connection refused → networking/bind/port mapping.
  • Page loads but UI says disconnected / unauthorized / pairing required → auth/origin/pairing/device-auth.

If you can, paste what you see + the output of the commands below.

1) On OCI, the Gateway often isn’t reachable because it’s still bound to loopback

Default is:

  • gateway.bind: "loopback" → listens on 127.0.0.1:18789 only (NOT reachable from the internet)

Check on the OCI box:

#
openclaw --version
openclaw gateway status
openclaw gateway status --json
ss -lntp | rg ":18789"

If ss shows 127.0.0.1:18789, you need to bind to LAN/tailnet for remote access (see #2).

2) Minimal config for password auth + remote bind

In openclaw.json:

{
  gateway: {
    bind: "lan",           // 0.0.0.0
#
    port: 18789,
    auth: {
      mode: "password",
      // prefer env var for the secret:
      // password: "${OPENCLAW_GATEWAY_PASSWORD}"
    },
  },
}

Then restart the gateway and re-check ss -lntp.

Docs: https://docs.openclaw.ai/gateway/configuration-reference

3) “Going to http://<ip>:18789” may be the wrong path

Depending on your config, Control UI can be at either:

#
  • http://<ip>:18789/ (default), or
  • http://<ip>:18789/openclaw/ if you set gateway.controlUi.basePath: "/openclaw".

Docs: https://docs.openclaw.ai/web/control-ui

4) If the page loads but it won’t connect: allowedOrigins + secure context

For non-loopback browser access, you must set:

  • gateway.controlUi.allowedOrigins (full origins)

Example:

{
  gateway: {
    controlUi: {
#
      allowedOrigins: ["http://<your-public-ip>:18789"],
    },
  },
}

Also: if you’re trying to use the Control UI over plain HTTP remotely, you can hit “device identity required” issues (browser non-secure context). The recommended fix is HTTPS via Tailscale Serve.

Docs: https://docs.openclaw.ai/web/control-ui#insecure-http

5) OCI-specific: you might have opened the VCN port but not the instance firewall / container port

If you’re running via Docker/Podman, you also need the port published (-p 18789:18789).

If not containerized, check host firewall:

#
sudo ufw status
sudo iptables -S | head

If you paste:

  1. openclaw gateway status
  2. ss -lntp | rg ":18789"
  3. whether you’re using Docker/Podman or bare metal
    …I can tell you exactly which of the above is the blocker.
molten night
red canyonBOT
#

That message is coming from the Control UI running in your browser, not from the gateway itself.

When you open the UI at http://<public-ip>:18789/, the browser treats it as a non-secure context (not HTTPS, not localhost), and WebCrypto/device-identity features are blocked → so OpenClaw refuses to connect the UI.

You’ve got 3 realistic options:

Option A (recommended): HTTPS via Tailscale Serve

Run Tailscale on the OCI box, then:

  • openclaw gateway --tailscale serve

Then open:

  • https://<magicdns>/ (or your configured gateway.controlUi.basePath)

If you want to force password auth even over Serve, set:

#

Open http://localhost:18789/ and it’ll be happy (localhost = secure enough for this).

Option C (break-glass): disable device auth for the Control UI (not recommended)

This is intentionally labeled dangerous in the docs:

{
  gateway: {
    controlUi: { dangerouslyDisableDeviceAuth: true },
  },
}

Only do this if the gateway is not meaningfully exposed (VPN-only + firewall), because it’s a big security downgrade.

If you tell me whether you’re trying to use the browser UI remotely long-term, or you only need the watch client to connect to the gateway WS API, I can point you at the safest setup for that (because “password over plain ws/http on a public IP” is… spicy).

molten night
red canyonBOT
#

If you mean: open the Control UI from another machine at http://<ip>:18789/ and have it connect — that’s intentionally blocked.

Reason: remote HTTP is a non-secure browser context, so the browser won’t allow the WebCrypto bits OpenClaw uses for Control UI device identity, and the gateway refuses the connection (“requires device identity (use HTTPS or localhost secure context)”). This is by design. Docs: https://docs.openclaw.ai/web/control-ui#insecure-http

So your choices are basically:

  1. Keep HTTP, but make it localhost (SSH tunnel)
    You still use HTTP in the browser, but it’s localhost so it’s a “secure context”:

    ssh -L 18789:127.0.0.1:18789 <user>@<host>
    

    then open http://localhost:18789/

  2. Use HTTPS (Tailscale Serve is the easy button)
    https://docs.openclaw.ai/gateway/tailscale

  3. Break-glass: allow remote HTTP by disabling Control UI device auth (not recommended)

#
   {
     gateway: {
       controlUi: {
         dangerouslyDisableDeviceAuth: true
       }
     }
   }

If you do this, please also lock it down (VPN only, or firewall the port to your IPs). A shared password over plain HTTP on a public IP is very easy to steal via MITM.

Also note: gateway.controlUi.allowInsecureAuth does not fix this one — it doesn’t bypass the device identity requirement (per the docs section above).

molten night
red canyonBOT
#

Yep — use the CLI so you don’t have to hand-edit JSON.

On the gateway machine (OCI), run:

openclaw config set gateway.controlUi.dangerouslyDisableDeviceAuth true --strict-json
openclaw gateway restart

If your gateway is bound to LAN and you’re accessing the UI from another device, you’ll usually also need an explicit origin allowlist, e.g.:

openclaw config set gateway.controlUi.allowedOrigins '["http://<YOUR_IP>:18789"]' --strict-json
openclaw gateway restart

Replace <YOUR_IP> with whatever you type into the browser.

molten night
red canyonBOT
#

That’s the Gateway’s auth rate limiter kicking in (429 Too Many Attempts). It happens when the UI (or something else) keeps trying to connect with a missing/wrong password.

1) Quick unblock

  • Wait ~5 minutes (default lockout is typically 300000ms), or
  • Restart the gateway (often clears the in-memory lockout):
openclaw gateway restart

2) Figure out who is spamming auth (you vs the internet)

On the gateway box:

openclaw logs --follow
#

Then try connecting once more and look for lines mentioning 429, rateLimit, too many, unauthorized and (ideally) the client IP.

Common causes:

  • You refreshed the Control UI tab: passwords are not persisted by the UI, so it reconnects without one and quickly trips the limiter.
  • Your port is on a public OCI IP and you’re getting random scanner traffic (this is extremely common the moment you open a port).

3) If it’s your browser reconnect-looping, reduce the pain (temporarily)

You can loosen the limiter:

openclaw config set gateway.auth.rateLimit.maxAttempts 50 --strict-json
openclaw config set gateway.auth.rateLimit.lockoutMs 30000 --strict-json
openclaw gateway restart

4) If it’s random internet traffic: you must firewall it

If 18789 is reachable from the public internet, you’ll get hammered. Best fix is to restrict ingress to your IP only (OCI security list/NSG + host firewall).

#

If you paste a couple log lines around the “too many authentication attempts” event (redact secrets), I can tell you immediately which case you’re in.

molten night
red canyonBOT
#

You should never need both. If it feels like “token + password”, it’s almost always one of these:

1) Gateway is still actually in token mode

On the gateway machine, check:

openclaw config get gateway.auth

You want to see mode: "password" (and ideally no token set). To force it:

openclaw config set gateway.auth.mode '"password"' --strict-json
openclaw config unset gateway.auth.token
openclaw gateway restart
#

(That weird quoting is just so --strict-json treats it as a real JSON string.)

2) Your browser has an old token saved and the Control UI prefers token if present

The Control UI persists token in localStorage, but does NOT persist password. So if you ever opened a ...#token=... URL (e.g. from openclaw dashboard) the UI will keep trying that token forever.

Fix (pick one):

  • Open the UI in an Incognito/Private window (fresh storage), or
  • Clear site data for http://<ip>:18789, or
  • Make sure you open a “clean” URL with no #token=... fragment.

After that, leave the token field empty and enter only the password.

If you paste the output of openclaw config get gateway.auth I can tell which case you’re in immediately.

molten night
red canyonBOT
#

You’re hitting device pairing (Gateway WS pairing), not the “DM pairing” stuff.

Can you disable device pairing for nodes?

For node connections: no — there isn’t a supported “pairing off” switch. The protocol requires device identity + pairing for new device IDs. The only “bypass” knob is Control UI only (gateway.controlUi.dangerouslyDisableDeviceAuth), and it doesn’t apply to node clients. (Docs callout: https://docs.openclaw.ai/gateway/protocol#device-identity--pairing)

The good news: it’s supposed to be one-time

If your watch client is repeatedly stuck in “pairing required”, it’s usually because the app is generating a new keypair / device.id every launch, so the gateway treats it as a brand new device every time.

What you want:

  1. Watch generates a keypair once and persists it
  2. device.id stays stable (derived from that keypair fingerprint)
  3. You approve once, then it connects forever

Headless “approve the watch” commands

On the gateway host, when the watch is trying to connect:

#
openclaw devices list
openclaw devices approve --latest

(or openclaw devices approve <requestId> if you want the explicit one)

Docs: https://docs.openclaw.ai/cli/devices

After approval: don’t keep using password forever

After pairing, the Gateway returns a device token in hello-ok.auth.deviceToken. Your watch app should persist that token and use it on reconnect — then you don’t need to type token/password again (and you won’t trip rate limits with wrong creds).

If you tell me whether your watch client currently persists (a) its keypair and (b) the returned deviceToken, I can point at the exact thing that’s causing the reconnect loop.

molten night
red canyonBOT
#

That error is super specific: the Gateway is rejecting your connect.params because connect.params.client.id is not one of the allowed literal IDs in the protocol schema.

client.id is not free-form. If you send something like "galaxy-watch", "wearos", a package name, etc., the validator throws exactly what you’re seeing (“must be equal to constant … must match a schema in anyOf”).

Fix

Pick one of the built-in IDs exactly (lowercase matters). For a custom watch “node” client, I’d use:

  • client.id: "node-host"
  • client.mode: "node"

If you want a unique label for your app/device, put it in:

  • client.displayName (free-form), and/or
  • client.instanceId (free-form), and/or
  • your stable device.id (derived from your persisted keypair fingerprint)

Minimal client object that will pass schema

#
"client": {
  "id": "node-host",
  "mode": "node",
  "version": "0.1.0",
  "platform": "android",
  "displayName": "Galaxy Watch6"
}

Also make sure your top-level connect.params includes minProtocol and maxProtocol (integers). If you don’t know the exact current protocol number yet, you can safely do something like minProtocol: 1 and maxProtocol: 9999.

If you paste this, I can pinpoint it instantly

Paste your current connect.params.client JSON (no secrets in there), and I’ll tell you exactly which field is invalid (it’s usually client.id or client.mode, or extra fields because additionalProperties: false).

molten night
red canyonBOT
#

device signature invalid means the gateway accepted your device.id, nonce, and signedAt (so you’re past the easy checks), but the Ed25519 signature doesn’t verify against the exact payload string the gateway reconstructs.

Here’s the exact payload format the gateway expects (from the current protocol implementation):

1) Make sure you’re signing the right payload (v3 recommended)

The gateway will try v3, then fall back to v2.

v3 payload (string, UTF‑8)

v3|<deviceId>|<clientId>|<clientMode>|<role>|<scopesCsv>|<signedAtMs>|<token>|<nonce>|<platformNorm>|<deviceFamilyNorm>

Where:

  • deviceId = sha256(rawPublicKeyBytes).hex (64 chars)
  • clientId = "node-host" (what you set)
  • clientMode = "node"
  • role = "node" or "operator" (must match what you send)
#
  • scopesCsv = scopes joined with commas with no spaces, in the same order you send (e.g. operator.admin or node.read,node.write)
  • signedAtMs = the same number you put in device.signedAt (milliseconds)
  • token = NOT the password. It’s auth.token if present, otherwise auth.deviceToken if present, otherwise empty.
    • If you’re doing password-only for the first connect: token should be empty → you must still include the empty field, so you’ll have ...|<signedAtMs>||<nonce>|...
  • nonce = the connect.challenge nonce (and must equal device.nonce)
  • platformNorm + deviceFamilyNorm = trimmed + ASCII-lowercased versions of the strings you send in client.platform / client.deviceFamily
    • If you sign with "Android" but send "Android", the gateway normalizes to "android" in v3, and you’ll fail unless you normalize the same way before signing.

v2 payload (easier to get working)

If you want a quick “just make it connect” path, sign v2 first (no platform/deviceFamily fields):

v2|<deviceId>|<clientId>|<clientMode>|<role>|<scopesCsv>|<signedAtMs>|<token>|<nonce>

The gateway will accept v2 signatures too.

2) Make sure your key + signature encoding matches

  • Algorithm: Ed25519 (not ECDSA/RSA, and don’t pre-hash the payload)
#
  • Signature bytes: 64 bytes
  • device.signature: base64url (no padding) of the signature bytes
  • device.publicKey: base64url (no padding) of the raw 32-byte Ed25519 public key
    • Do not send X.509/SPKI DER bytes.
    • If the only thing you can get is SPKI DER, the raw public key is usually: strip this hex prefix if present:
      • 302a300506032b6570032100 and then the next 32 bytes are the key.

3) The usual culprits (90% of cases)

  1. You’re signing v3 but not ASCII-lowercasing platform / deviceFamily before signing.
  2. You forgot the empty token field when password-only (payload must keep the ||).
  3. You sign with role/scopes that don’t exactly match what you send in connect.params.
  4. You’re base64url-encoding the wrong thing (e.g., SPKI instead of raw 32 bytes, or signature as hex).

If you paste your constructed payload string (with token redacted if needed) and tell me:

  • what you set for role + scopes
  • what you send in client.platform / client.deviceFamily
  • whether auth.token or auth.deviceToken is present
#

…I can usually spot the mismatch immediately.

molten night
#

Great! Pairs. Problem. My node is "WatchClaw (SM-REDACTED)" with a space, meaning OpenClaw has no idea how to remove it.

red canyonBOT
#

Spaces are fine — you just need to quote the node name when you pass it to the CLI.

Use quotes (works for any command that takes --node)

openclaw nodes status --node "WatchClaw (SM-REDACTED)"
# or
openclaw nodes run --node "WatchClaw (SM-REDACTED)" --raw "uname -a"

Better: rename it to something without spaces

(You only have to quote it this one time.)

openclaw nodes rename --node "WatchClaw (SM-REDACTED)" --name "WatchClaw-SM-REDACTED"

Best (long-term): use the nodeId instead of the display name

Run: