How a machine is paired: install the Soba app, paste the connect link, approve in a browser. No terminal, no toolchain, and no credential is ever displayed or pasted.
A user connects their machine with Soba App, the app that
carries the worker inside it. There is nothing to install first: no Node, no
toolchain, no package manager.
1. Install the app#
They download it from soba.so/app and open it. It runs as a
login item rather than a system service, so it needs no administrator and starts with
them.
2. Paste the connect link#
The app asks for one thing: the link your settings page shows them.
https://soba.so/c/pk_soba_<your connect key>
The pk_soba_ in it is your app's connect key, minted on the Keys page. It names
the app and carries no secret half, which is why it is safe to put in front of a user:
starting a pairing achieves nothing until one of your signed-in users approves it,
and it expires in fifteen minutes either way. Never put your sk_soba_ key here.
<ConnectCompute /> renders the link and the download button for
you.
3. Approve in the browser#
The app shows a code and the page to approve it on.
Your code: ACDE-F234
Approve at: https://soba.so/link
They approve where they are already signed in to your app, which is also the only
place they can meaningfully judge what they are approving. The app pairs and starts
serving runs, and it keeps doing so after they close the window and after a reboot.
No credential is ever displayed or pasted#
This is the device authorization grant (RFC 8628), the shape gh auth login uses. The
flow it replaces was: open the app, find settings, mint a token, copy it, paste it into
a terminal. Four steps, one of them a copy-paste of a bearer credential into shell
history.
Here the token is never shown to anyone. It is written to ~/.soba/worker.json, mode
0600, by the worker itself.
Two things it refuses to do#
The pairing page must be https. Loopback is excepted for development. That URL
arrives over the network, and the control plane is the component we ask people to trust
least, so an unchecked value is a phishing page one compromise away.
Refusing to send you to http://evil.example/link: pairing pages must be https.
The browser is never opened without being asked. The URL is always shown first. A
URL someone has read and agreed to is a different thing from one a program opened on
their machine.
Sending someone to the approval page#
Your user has an account with you, not with us, so we cannot sign them in. Mint a
short-lived end-user session on your server and redirect them to the URL it hands
back. This is the same trick Stripe Checkout uses, and it is what makes the connect
step work in Vue, Svelte, Rails and Django on day one.
JavaScript
const res = await fetch("https://www.soba.so/v1/end_users/session", {
method: "POST",
headers: {
authorization: `Bearer ${process.env.SOBA_KEY}`, // your sk_soba_ key
"content-type": "application/json",
},
body: JSON.stringify({ user: session.user.id }), // the same id you send on a run
});
const { url } = await res.json(); // https://soba.so/link#est_…
redirect(url);
The session lives ten minutes, acts for exactly one of your users, and travels in the
URL fragment: the one part of a URL a browser never sends to a server, so it is
not in an access log, a proxy log, or the Referer of any link on the page.
The page shows what is asking to be paired, takes the code off them, and then waits for
the machine to actually connect before it says it worked. A pairing that never dials in
looks exactly like a successful one right up until the first run fails.
When they start from the app#
Somebody who opens the Soba app before visiting your app has to be sent somewhere that
can identify them, and we cannot: their account is with you.
So set a connect page on the app's Settings page. When you do, that URL becomes the
one the app sends them to, with ?code= appended, and the person never passes through
soba.so at all:
Your code: ACDE-F234
Approve at: https://your-app.example.com/settings/compute?code=ACDE-F234
Your page signs them in, mints a session as above, and forwards them:
https://soba.so/link#est_…&code=ACDE-F234
Both halves in the fragment, so neither reaches a log. Leave the connect page unset
and the printed URL falls back to soba.so/link, which can only tell them to start
again inside your app. That works, and it costs them a detour.
The URL has to be https (loopback excepted for development), which is checked when
you save it and again before it is opened.
Machines with nobody at them#
The Soba app has a window, which is the right shape for a person's own
laptop and the wrong one for a server. A headless box (a VPS, an always-on machine in
a cupboard) pairs with the command-line worker instead, a signed binary from
soba.so/app. It performs the same device grant and prints the
same code, and there is no browser needed on the machine being paired.
See Keeping it running for installing it as a service, and
Worker CLI for every verb and flag.
The one line the whole thing exists for#
The pairing token is bound to the end user who approved it, never to anything the
machine claimed about itself. A run may only ever reach a machine owned by the user it
is attributed to, and that is the difference between "each person uses their own
subscription" and "an app harvests subscriptions into a pool".
Where the token goes#
Into ~/.soba/worker.json, mode 0600. It travels in an Authorization: Bearer
header, never in the URL, because a URL ends up in access logs, proxy logs and error
reports.
Tokens carry a TTL and can be revoked or rotated per machine. When one expires or is
revoked, the machine is told which of those happened and the fix is to pair again. See
Troubleshooting a machine.