I wanted the Higgsfield 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.
The word is localhost
.
The server is headless: no monitor, no browser, reached only over SSH from a laptop. That detail is the whole story.
Higgsfield 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>
, 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=...
. The listener catches that request, reads the code out of it, trades it for a token, and you're in.
On a laptop this is seamless because the browser and the listener are the same machine. localhost
resolves to the same place for both halves of the handshake. The whole design quietly depends on that.
On 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
, the browser dutifully connects to the laptop's localhost — where nothing is listening — and shows:
Unable to connect
The login succeeded. The callback failed. The code was issued — it just landed on the wrong machine's front door.
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.
The reflex fix is an SSH tunnel. Forward the callback port from the laptop to the server, so the laptop's localhost:<port>
actually reaches the server's listener:
ssh -L 8799:localhost:8799 you@server
Sound. 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.
higgsfield auth login
has a --port
flag, so I told it to use 8799
to match my tunnel. Clerk rejected the whole login:
error: invalid_request
The 'redirect_uri' parameter does not match any of the
Client's pre-registered redirect URIs.
Here'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
wasn't in the club.
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
flag 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.
The tunnel isn't wrong. It's just more moving parts than the problem actually needs.
The 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>
. The listener doesn't care who delivers that message. It cares that the message arrives with a state
matching the one it issued.
So I stopped trying to route the browser to the listener at all. I ran higgsfield auth login
on the server (letting it pick its own registered port — it landed on 8766
), opened the printed URL in my laptop browser, approved, and let the redirect fail — Unable to connect
, as expected. Then I copied the dead URL straight out of the browser's address bar:
http://localhost:8766/callback?code=OWVH...F1L&state=kg6P...TCY
That 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:
curl "http://localhost:8766/callback?code=OWVH...F1L&state=kg6P...TCY"
The listener saw a request on its port, checked the state
, matched it, exchanged the code for a token, and printed Successfully authenticated.
No 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.
Two things make this safe to lean on, and both are worth saying out loud:
state
parameter is the check that makes replay legitimate.localhost
is a per-machine word.localhost
is 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
, you're fighting the security model, not a config typo. Use the app's own default port.curl
on the machine that's listening.state
is what makes hand-delivery honest.state
isn'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.