{"slug": "logging-into-the-higgsfield-cli-on-a-server-with-no-browser", "title": "Logging Into the Higgsfield CLI on a Server With No Browser", "summary": "A developer encountered a login failure when using the Higgsfield CLI on a headless server because the OAuth 2.0 PKCE flow assumes the browser and the HTTP listener share the same localhost. The redirect URI allowlist prevented using a custom port for an SSH tunnel, forcing reliance on the CLI's default registered ports. The developer realized the callback is a simple HTTP GET and can be handled by manually delivering the code to the listener.", "body_md": "I wanted the [Higgsfield CLI](https://github.com/higgsfield-ai/cli) on my server — a terminal front-end to a pile of image and video generation models, which makes it scriptable and cron-able in a way the web UI never will be. The install was one line. Then it asked me to log in, and a specific, recurring failure showed up that has nothing to do with Higgsfield and everything to do with a word that means two different things at once.\n\nThe word is `localhost`\n\n.\n\nThe server is headless: no monitor, no browser, reached only over SSH from a laptop. That detail is the whole story.\n\nHiggsfield authenticates with OAuth 2.0 PKCE, the way most modern CLIs do, and the polite version goes like this: the CLI opens a small HTTP listener on `localhost:<port>`\n\n, prints a login URL, and waits. You open the URL in a browser, approve, and the identity provider (Higgsfield fronts theirs with Clerk) redirects your browser to `http://localhost:<port>/callback?code=...`\n\n. The listener catches that request, reads the code out of it, trades it for a token, and you're in.\n\nOn a laptop this is seamless because the browser and the listener are the same machine. `localhost`\n\nresolves to the same place for both halves of the handshake. The whole design quietly depends on that.\n\nOn a headless server it falls apart, because the two halves are now on different machines. The listener is on the server. The browser is on my laptop. When Clerk redirects the browser to `http://localhost:<port>/callback`\n\n, the browser dutifully connects to *the laptop's* localhost — where nothing is listening — and shows:\n\n```\nUnable to connect\n```\n\nThe login succeeded. The callback failed. The code was issued — it just landed on the wrong machine's front door.\n\n**A loopback OAuth flow assumes the browser and the listener share a host.** That assumption is invisible on a laptop and load-bearing on a headless box.\n\nThe reflex fix is an SSH tunnel. Forward the callback port from the laptop to the server, so the laptop's `localhost:<port>`\n\nactually reaches the server's listener:\n\n```\nssh -L 8799:localhost:8799 you@server\n```\n\nSound. It works. But I hit a wall trying to pin the port, and the wall is worth understanding because it's not a bug — it's the security model doing its job.\n\n`higgsfield auth login`\n\nhas a `--port`\n\nflag, so I told it to use `8799`\n\nto match my tunnel. Clerk rejected the whole login:\n\n```\nerror: invalid_request\nThe 'redirect_uri' parameter does not match any of the\nClient's pre-registered redirect URIs.\n```\n\nHere's the thing PKCE doesn't remove: the **redirect URI allowlist**. PKCE lets a CLI skip shipping a client secret — the code challenge proves the token request came from whoever started the login — but the provider still refuses to send a code to any callback URL the app didn't register in advance. That allowlist is what stops a malicious page from pointing your authenticated redirect at *its* server. Higgsfield registered a specific handful of ports with Clerk, and those are the only ones that work. `8799`\n\nwasn't in the club.\n\n**The redirect URI is a registered constant, not a free parameter.** You don't get to pick the port; you get to pick from the ports the app's developer already blessed. Drop the `--port`\n\nflag and the CLI falls back to its own default and it works — but now your tunnel has to match a port you don't fully control, and if that port is already busy on the server (mine collided with another service on the default), the CLI silently falls back again to a *different* registered port, and your tunnel is aimed at the wrong one.\n\nThe tunnel isn't wrong. It's just more moving parts than the problem actually needs.\n\nThe realization that made this easy: **the callback is not a handshake, it's a message.** It's a plain HTTP GET whose entire payload is visible in the URL — `?code=<the code>&state=<the anti-forgery token>`\n\n. The listener doesn't care *who* delivers that message. It cares that the message arrives with a `state`\n\nmatching the one it issued.\n\nSo I stopped trying to route the browser to the listener at all. I ran `higgsfield auth login`\n\non the server (letting it pick its own registered port — it landed on `8766`\n\n), opened the printed URL in my laptop browser, approved, and let the redirect fail — `Unable to connect`\n\n, as expected. Then I copied the dead URL straight out of the browser's address bar:\n\n```\nhttp://localhost:8766/callback?code=OWVH...F1L&state=kg6P...TCY\n```\n\nThat URL is the whole message. The listener is sitting on the server's own localhost, exactly where the CLI is watching. So I replayed it there:\n\n```\ncurl \"http://localhost:8766/callback?code=OWVH...F1L&state=kg6P...TCY\"\n```\n\nThe listener saw a request on its port, checked the `state`\n\n, matched it, exchanged the code for a token, and printed `Successfully authenticated.`\n\nNo tunnel. The browser did the one thing only a browser with my logged-in Higgsfield session could do — prove I'm me — and then I hand-carried the resulting code the last hop the browser couldn't make.\n\nTwo things make this safe to lean on, and both are worth saying out loud:\n\n`state`\n\nparameter is the check that makes replay legitimate.`localhost`\n\nis a per-machine word.`localhost`\n\nis quietly assuming the browser and the service are the same box. On a headless server they aren't, and that mismatch — not the tool — is your bug.`invalid_request`\n\n, you're fighting the security model, not a config typo. Use the app's own default port.`curl`\n\non the machine that's listening.`state`\n\nis what makes hand-delivery honest.`state`\n\nisn't a workaround around the security — it's the security working.The tunnel is the textbook answer, and some days you'll want it. But when a login on a headless machine tells you it *can't connect to localhost*, remember that it already succeeded — the code exists — and the last hop is just a URL you're allowed to carry across yourself. This worked cleanly for Higgsfield; it'll work for any CLI whose login is a loopback redirect, which by now is most of them.", "url": "https://wpnews.pro/news/logging-into-the-higgsfield-cli-on-a-server-with-no-browser", "canonical_source": "https://dev.to/henry_dan_81513dd35a2f540/logging-into-the-higgsfield-cli-on-a-server-with-no-browser-34gi", "published_at": "2026-07-08 04:08:43+00:00", "updated_at": "2026-07-08 04:28:40.251448+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Higgsfield CLI", "Clerk", "OAuth 2.0", "PKCE", "SSH"], "alternates": {"html": "https://wpnews.pro/news/logging-into-the-higgsfield-cli-on-a-server-with-no-browser", "markdown": "https://wpnews.pro/news/logging-into-the-higgsfield-cli-on-a-server-with-no-browser.md", "text": "https://wpnews.pro/news/logging-into-the-higgsfield-cli-on-a-server-with-no-browser.txt", "jsonld": "https://wpnews.pro/news/logging-into-the-higgsfield-cli-on-a-server-with-no-browser.jsonld"}}