cli-authv0.1.0-beta

Production-grade OAuth for your CLI

A batteries-included, fully pluggable authentication library. Four flows. Token storage, refresh, cross-process locking, keychain integration. Every piece handled, every piece swappable.

$npm install cli-auth
MIT licenseNode.js 22+Bun 1.1+ESM onlyTypeScript
~/projects/your-cli — zsh

Four flows. One library.

Pick a strategy. The plumbing comes with it.

Device codeRFC 8628

The one you want for servers, Docker shells, and any box where opening a browser isn't an option.

  • Polling loop handles slow_down back-off and the expiry deadline for you
  • User code surfaced through an onAuthorization callback so you decide how to show it
  • Refreshes automatically when the access token nears expiry
When to reach for this → Headless, SSH, or remote. Anywhere your CLI lives somewhere a browser cannot reach. The user signs in from their laptop while your CLI polls in the background.
device-code.ts
1import { createCliAuth, keyringStorage } from 'cli-auth';2import { Entry } from '@napi-rs/keyring';3 4const auth = createCliAuth({5  strategy: 'device-code',6  provider: {7    metadata: {8      deviceAuthorizationEndpoint: 'https://your-tenant.logto.app/oidc/device/auth',9      tokenEndpoint: 'https://your-tenant.logto.app/oidc/token',10    },11  },12  clientId: 'your-cli-client',13  storage: keyringStorage({ entry: new Entry('your-cli', 'tokens') }),14  scope: 'openid offline_access',15});16 17await auth.login({18  onAuthorization: ({ userCode, verificationUri }) => {19    console.log(`Visit ${verificationUri} and enter ${userCode}`);20  },21});22 23const accessToken = await auth.getToken();

The stuff nobody tells you CLI auth needs.

Every note below is a real bug, race condition, or spec clause we've already handled.

slow_down back-off
PKCE verifier + challenge
loopback on 127.0.0.1
O_CREAT | O_EXCL
atomic write + rename
cross-process lock
recheck after lock
0600 permissions
macOS Keychain
Windows Credential Store
Linux Secret Service
refresh token single-use
expiry threshold
per-resource token cache
proxy / OTel fetch hook
branded callback page
custom parameters
TypeScript discriminated unions
…and tests.

Swap any piece. Nothing is a black box.

Storage, locking, fetch, and the callback page are all hooks. Nothing hardcoded. Click a slot and watch the config rewrite itself.

Swap the storage
macOS Keychain, Windows Credential Store, or Linux Secret Service, whichever the OS provides. Via @napi-rs/keyring.
cli-auth.config.ts
1import { createCliAuth } from 'cli-auth';2 3const auth = createCliAuth({4  strategy: 'authorization-code',5  provider: { /* ... */ },6  clientId: 'your-cli-client',7  storage: keyringStorage({ entry: new Entry('your-cli', 'tokens') }),8});

Bring your own identity provider

Any OAuth 2.0 / OIDC-compliant server works. Point the library at your endpoints and it runs. We didn't build this only for Logto.

Logto
Auth0
Okta
Keycloak
Microsoft Entra
Google
Authelia
Any OIDC 2.0 server
Logto
Auth0
Okta
Keycloak
Microsoft Entra
Google
Authelia
Any OIDC 2.0 server

Questions people ask

How is this different from openid-client?

Does it work with Auth0, Okta, or my own provider?

How does cross-process locking actually work?

What if the OS keyring isn't available?

Which runtimes are supported?

Can I brand the post-login callback page?

Ontgrendel meer met Logto Cloud

CLI authentication without writing it yourself.