Tutorial

Pair a Machine in 30 Seconds Without SSH Keys (Capability Tokens)

Akshay Sarode
Direct answer

The tray icon mints a 16-byte random pairing token with a 600-second TTL. The user clicks 'Pair this machine,' the browser jumps to the dashboard, the token is consumed in a constant-time compare, and a Firebase ID token is exchanged for a daemon session. No SSH keys to copy, no IPs to remember, no ports to forward.

SSH key pairing has aged well — for a 1990s threat model. The flow is "generate keypair, copy public half to the server, server now trusts you forever." It works. It's also five steps and three terminals if you've never done it.

For a daemon that's already locally authenticated to your account (Google sign-in, Firebase ID token), there's a faster path: capability tokens issued by the local UI.

The 30-second flow

  1. Open the dashboard at celistra.dev in any browser. Sign in with Google.
  2. On the machine you want to pair, click the Celistra tray icon → Pair this machine.
  3. Browser jumps to celistra.dev/pair?token=<random>.
  4. Done. Machine is paired.

The whole exchange takes <1 second once the browser navigation lands.

Why a token, not a QR code?

The token works for the same-device case (you're at the machine). For different-device pairing (your phone driving a friend's laptop), a QR encoding the same token works — the dashboard shows it as a QR after generation.

The crypto

// Tray click
token := make([]byte, 16)
crypto/rand.Read(token)
expiry := time.Now().Add(600 * time.Second)
storeChallenge(token, expiry)
openBrowser("https://celistra.dev/pair?token=" + hex.EncodeToString(token))

// Dashboard hits POST /v1/auth with the token + Firebase ID token
// Daemon validates Firebase ID token offline against Google's JWKS,
// then constant-time compares the pairing token, then burns it.

Three properties matter:

What the token authorizes

The token by itself does nothing. The dashboard sends both the token AND a Firebase ID token. The daemon validates both:

  1. Firebase ID token: signature checked offline against Google's JWKS (cached locally), aud matches our project, iss matches https://securetoken.google.com/<project>, exp is in the future.
  2. Pairing token: constant-time match against the in-memory challenge.

If both pass, the daemon writes the user's UID into cfg.AllowedUIDs. Subsequent requests authenticate via Firebase ID token alone — no token needed.

What this replaces

SSH key flowCapability token flow
Generate keypair on client (ssh-keygen)Already done — Firebase identity
Copy public key to server (ssh-copy-id)Click tray button
Add server to ~/.ssh/configAuto-added to fleet view
Open port 22 in firewallDaemon binds 127.0.0.1 only
Test (ssh user@host)Works immediately

Edge cases

Browser is already at celistra.dev signed in as the wrong account. The pair URL accepts the token + the current Firebase ID token. If it's the wrong account, the daemon's AllowedUIDs ends up adding both — user can revoke from the tray. Practically, sign out first.

Token expires before user clicks. Click "Pair this machine" again. New token, new TTL.

User pairs the same machine twice. Idempotent — adds the same UID to AllowedUIDs if not present.

Compromised device. "Revoke all sessions" in the tray calls a Cloud Function that runs admin.auth().revokeRefreshTokens(uid) against the account. Every Firebase ID token mints from a refresh token; revoke them all and the daemon's offline JWKS check still accepts existing ID tokens until they expire (max 1 hour). Ride out the hour, or rotate the daemon secret too.

What we ship

This is the exact pairing flow Celistra uses. The tray code is in the daemon repo (celistrad/tray.go) and is short enough to read in one sitting. The dashboard side (src/pages/Pair.tsx) is similarly small. The whole thing is a few hundred lines of code.

FAQ

Does this work without internet?

The pairing step needs internet (Firebase Auth). After pairing, normal use is LAN-direct (no internet) when the dashboard and the daemon are on the same network.

Can a malicious local app steal the pairing token?

If the malicious app can read the daemon's memory or hit 127.0.0.1:33120, yes. The token's defense is that it's single-use and short-lived. The bigger defense is don't run untrusted code as your user.

How is this different from OAuth device-code flow?

OAuth device-code is a similar pattern (out-of-band display of a code, in-band consumption). This is the same shape but the 'device' is the daemon and the 'authorization' is just 'user is signed into the dashboard.' Simpler because the trust comes from the OS user, not from a remote server.