#How do I use token and password auth?
1 messages · Page 1 of 1 (latest)
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), orconnect.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:
- a custom WS client that talks directly to the Gateway, or
- running
openclawitself on the watch in some “remote mode” setup,
…I can point you at the exact config surface you should use.
Set a password. Now, when I type in the IP, it doesn't work (I'll fix that), but in a browser, going to <MYIP>:18789 doesn't work. OCI. I allowed the port (TCP).
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 on127.0.0.1:18789only (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), orhttp://<ip>:18789/openclaw/if you setgateway.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:
openclaw gateway statusss -lntp | rg ":18789"- whether you’re using Docker/Podman or bare metal
…I can tell you exactly which of the above is the blocker.
Got it working, but now says that it either needs https or localhost (cannot paste error due to Discord being finicky).
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 configuredgateway.controlUi.basePath)
If you want to force password auth even over Serve, set:
{
gateway: {
auth: { mode: "password" },
auth: { allowTailscale: false }, // require password, don’t accept tailnet identity headers
}
}
Docs: https://docs.openclaw.ai/web/control-ui#insecure-http and https://docs.openclaw.ai/gateway/tailscale
Option B: SSH tunnel (quick + clean for testing)
Keep the gateway bound to loopback, then from your computer:
ssh -L 18789:127.0.0.1:18789 <user>@<oci-host>
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).
Meant to say "control ui requires device identity (use HTTPS or localhost secure context)". I just want to use HTTP. That's all.
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:
-
Keep HTTP, but make it localhost (SSH tunnel)
You still use HTTP in the browser, but it’slocalhostso it’s a “secure context”:ssh -L 18789:127.0.0.1:18789 <user>@<host>then open
http://localhost:18789/ -
Use HTTPS (Tailscale Serve is the easy button)
https://docs.openclaw.ai/gateway/tailscale -
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).
Can you give me the command? I'm not very good at JSON...
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.
Docs (this is the “break-glass” path): https://docs.openclaw.ai/web/control-ui#insecure-http and openclaw config command: https://docs.openclaw.ai/cli/config
Security note: doing this over plain HTTP on a public IP is unsafe unless you firewall it down to only the IPs you control.
Great. Now I'm getting too many authentication attempts now. Yay.
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.
Here's another issue. Now I need the gateway token AND the password. Not good. At all.
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.
Now, how do I disabling pairing? It's definitely affecting the app as a whole to not connect, because it just automatically tries to connect with no hope. Yes, I know this is insecure. I have put multiple measures in place.
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:
- Watch generates a keypair once and persists it
device.idstays stable (derived from that keypair fingerprint)- 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.
So basically, I got it all working-ish. Final step is connecting. I get this error everytime. I don't know why, and my fixes don't work. I assume you can help.
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/orclient.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).
Works. Now receiving device signature invalid 1008. Again, unsure how to solve this.
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.adminornode.read,node.write)signedAtMs= the same number you put indevice.signedAt(milliseconds)token= NOT the password. It’sauth.tokenif present, otherwiseauth.deviceTokenif present, otherwise empty.- If you’re doing password-only for the first connect:
tokenshould be empty → you must still include the empty field, so you’ll have...|<signedAtMs>||<nonce>|...
- If you’re doing password-only for the first connect:
nonce= theconnect.challengenonce (and must equaldevice.nonce)platformNorm+deviceFamilyNorm= trimmed + ASCII-lowercased versions of the strings you send inclient.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.
- If you sign with
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 bytesdevice.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:
302a300506032b6570032100and then the next 32 bytes are the key.
3) The usual culprits (90% of cases)
- You’re signing v3 but not ASCII-lowercasing
platform/deviceFamilybefore signing. - You forgot the empty
tokenfield when password-only (payload must keep the||). - You sign with
role/scopesthat don’t exactly match what you send inconnect.params. - 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.tokenorauth.deviceTokenis present
…I can usually spot the mismatch immediately.
Great! Pairs. Problem. My node is "WatchClaw (SM-REDACTED)" with a space, meaning OpenClaw has no idea how to remove it.
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: