Skip to content
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.

You get
A command-line tool that signs in through the browser and keeps its tokens; personal access tokens for CI.
For
Backend developer
Time
About 15 min
Products
Sign-in
Machines and AI
How it works: Command-line toolDevice code: sign in on any device. 1. Your CLI → Anchr.ing: Asks for a device code. 2. Anchr.ing → Your CLI: Code + URL. 3. Your CLI → User: Shows code + URL. 4. User → Anchr.ing: Signs in on any device. 5. Your CLI → Anchr.ing: Polls until approved. 6. Anchr.ing → Your CLI: Tokens.Device code: sign in on any deviceUserYour CLIAnchr.ing1Asks for a device code2Code + URL3Shows code + URL4Signs in on any device5Polls until approved6TokensHow it works: Command-line toolDevice code: sign in on any device. 1. Your CLI → Anchr.ing: Asks for a device code. 2. Anchr.ing → Your CLI: Code + URL. 3. Your CLI → User: Shows code + URL. 4. User → Anchr.ing: Signs in on any device. 5. Your CLI → Anchr.ing: Polls until approved. 6. Anchr.ing → Your CLI: Tokens.Device code: sign in on any deviceUserYour CLIAnchr.ing1Asks for a device code2Code + URL3Shows code + URL4Signs in on any device5Polls until approved6Tokens

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 a file of the app's own, ~/.config/anchring/tokens/<issuer host>/<client id>.json (respects XDG_CONFIG_HOME; file 0600, atomic writes; tokenFile picks another), 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=anchr_pat_test_…   # created on the user's account page
curl -H "Authorization: Bearer $ACME_TOKEN" https://api.acme.example/api/reports

Full example (Command-line tool): Node

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

Try it in your own test environment

Every feature works in test on the free plan, so the code above runs against your real sign-in.