Skip to content
anchr.ing
Guide

Sign in from a command-line tool

The device flow for CLIs: the user approves in a browser, tokens persist in ~/.config/anchring and refresh on their own. Personal access tokens for CI.

Register a public, device grant app (kind cli) with your API's audience, e.g. acme-api.

cli/login.ts
import { createAuth } from '@anchring/auth/cli';

const auth = createAuth({ project: 'acme', env: 'test', clientId: 'test_…' }); // the CLI app's client id

await auth.device.signIn({ open: true });
// Open acme.test.anchr.ing/device and enter ABCD-1234
await auth.fetch('https://api.acme.example/reports');
await auth.signOut();

The user opens the printed address in any browser, signs in (passkey, password, email code) and approves the code. Tokens are stored in ~/.config/anchring/tokens.json (respects XDG_CONFIG_HOME; file 0600, atomic writes) and refreshed automatically; processes sharing the file refresh one at a time under a lock file. Pass storage to use the OS keychain instead.

device.signIn options: onCode (show the code yourself), open (launch the browser; only https: URLs, or http: on loopback, never through a shell), scope and signal (cancel).

Not signed in

import { createAuth, SignInRequiredError } from '@anchring/auth/cli';

try {
  await auth.fetch('https://api.acme.example/reports');
} catch (e) {
  if (e instanceof SignInRequiredError) console.error('Not signed in: run `acme login`.');
  else throw e;
}

CI and scripts

For CI, users create a personal access token on their account page (name, scopes, at most a year) and your CLI or script sends it as a bearer. The API accepts it when its middleware has pat: { audience } (see Protect your API).

terminal
ACME_TOKEN=kh_pat_test_…   # created on the user's account page
curl -H "Authorization: Bearer $ACME_TOKEN" https://api.acme.example/api/reports

Every option, error and token type: the SDK README.