Add browser login to your CLI
Let customers sign in to their existing account and make a verified request to your API.
Give your customer one command: acme login. Their browser opens your sign-in page, they approve access, and the CLI checks their identity through your API before saving the session.
This guide configures that experience for your generated Node.js CLI. Your provider must support public-client Authorization Code with PKCE, and your API must accept its access tokens. Start with the provider setup notes for your existing account system.
Register your CLI
Create a public OAuth client for the generated CLI. Enable Authorization Code with PKCE and register a loopback callback. For a provider requiring an exact callback, use a fixed local port, such as http://127.0.0.1:43821/callback, in both its registration and Typeship settings.
The CLI cannot keep a client secret private. Only its public client ID belongs in generated configuration. Enable refresh tokens if your provider supports them, and request the scopes your API actually enforces. Some providers require offline_access for refresh tokens.
Use separate OAuth applications when products have different trust requirements. A backend application can keep a confidential client secret; a distributed CLI cannot. Sharing your API settings does not make their credentials interchangeable.
Configure login and identity
Your Definition must declare authentication for the operations that need it. For OAuth, include your provider's authorization and token endpoints and the API's scopes in OpenAPI. A bearer-authenticated API can also use the shared OAuth settings below. GraphQL uses its configured security requirements.
Add an authenticated identity read, such as GET /me, with no required arguments. For a response containing id and account_id, configure:
{
"auth": {
"oauth_server": {
"issuer": "https://auth.example.com",
"scopes": ["api:read", "offline_access"]
},
"oauth_applications": {
"cli": {
"client_id": "YOUR_PUBLIC_CLIENT_ID",
"login_method": "browser",
"redirect_uri": "http://127.0.0.1:43821/callback"
}
},
"oauth_application": "cli",
"identity_verification": {
"operation": "me.get",
"subject_field": "/id",
"account_field": "/account_id"
}
}
}Replace the issuer, client ID, scopes, operation name, and identity paths with your values. The issuer must match your provider's metadata exactly, including any path or trailing slash. If needed, set oauth_server.discovery_url to its exact metadata URL.
Use oauth_server.audience only when your provider requires an audience parameter. Use oauth_server.resource when it requires an OAuth resource parameter. They are distinct settings; choose the value identifying your API according to your provider.
Save the JSON above as auth-config.json, then generate from your specification:
npx -y @typeship-ax/cli@latest generate run \
--definition '{"url":"https://api.example.com/openapi.json"}' \
--target '{"generator":"cli"}' \
--config "$(cat auth-config.json)" \
--out ./api-cliReplace the specification URL with yours. Keep the configuration file for regeneration and keep credentials out of it.
Make the first authenticated request
Build the generated package and sign in:
cd api-cli
npm install
npm run build
node dist/cli.js login
node dist/cli.js whoamiComplete sign-in and consent in your browser. The CLI exchanges the authorization code using PKCE, checks your API's identity response, and saves the session with an OS-protected encryption key.
You are connected when your API returns the intended account. Run a read command from --help to see your own API's data. Confirm an operation outside the grant's permissions is denied before distributing the CLI.
For separate accounts, customers can run login --profile work --account acct_123. The account flag verifies the returned identity. It does not select an account in the browser. If you map an organization ID, the same rule applies to --organization; selection belongs in your provider's flow.
The Console records whether the generated CLI completed browser OAuth and whether your API rejected anonymous access, accepted the resulting credential, and returned the expected identity. Test refresh, revocation, and a scoped API command before distributing the package. A compatible local MCP process can reuse the CLI profile and refresh it without a restart.
Support remote terminals with device login
Device authorization is a separate provider capability. It prints a code and verification URL that the customer can open on another computer while the CLI polls for approval.
Register a public client with the device grant enabled, then keep your issuer, client ID, API scopes, and identity settings and change:
{
"auth": {
"oauth_applications": {
"cli": { "client_id": "YOUR_PUBLIC_CLIENT_ID", "login_method": "device" }
},
"oauth_application": "cli"
}
}This is a configuration fragment to merge into your existing settings. The CLI discovers device_authorization_endpoint and the token endpoint. When your provider requires explicit endpoints, use oauth_server.device_authorization_url and oauth_server.token_url. An endpoint override does not adapt a proprietary request or token format into standard OAuth.
Customers can also request the flow with login --device. Test actual device approval and refresh for your provider; browser PKCE verification does not establish device support. login --no-browser only prints the regular browser URL and still needs the callback on the CLI's computer.
Keep customizations through regeneration
Store shared settings and public OAuth applications in your Project or checked-in generation configuration. Keep provider sign-in, consent pages, credential issuance, and API authorization in your application. Regenerate the CLI to change login defaults; do not patch its generated login code.
For an SDK, put credential callbacks in the consuming application. For remote MCP, keep your credentialsFor implementation in an application wrapper outside the generated output directory. See Authentication for each target's responsibilities.
Use your own browser approval flow
Choose this route when your application can issue an API key or bearer credential after a customer approves access. It works with your existing session provider and requires backend implementation. It is a custom challenge-and-poll protocol, not the OAuth authorization-code grant.
1. Connect the approval service
Declare your API's bearer or API-key scheme, then set:
{
"auth": {
"approval_url": "https://api.example.com/cli-auth"
}
}Add this public configuration to your Project or generation settings, then regenerate. The CLI appends /start, /status, and /revoke to the URL. It stores the returned credential in the first generated scalar auth option; use this route for a single bearer token or API key, rather than an API requiring several simultaneous credentials.
Choose one default login route. When both a usable OAuth application and approval_url are configured, OAuth takes precedence. Passing credentials explicitly to login bypasses either browser flow.
2. Accept a login request
Implement POST /cli-auth/start. The CLI generates a random verifier, keeps it locally, and sends its SHA-256 hash encoded as base64url:
{
"code_challenge": "BASE64URL_SHA256_VERIFIER",
"name": "acme CLI on customer-laptop",
"source": "cli"
}Validate input, rate-limit requests, and create a random, short-lived session. Store the challenge, expiry, requested display name, and a pending state. Treat name and source as untrusted display information; they do not establish identity or permissions.
Return HTTP 200 with:
{
"session": "RANDOM_SESSION_ID",
"verification_url": "https://app.example.com/cli/approve?session=RANDOM_SESSION_ID",
"expires_in": 600,
"interval": 3
}Use HTTPS and construct the approval URL from your configured application origin. The response contains no API credential. The CLI opens this page, or prints the URL when run with --no-browser or under an agent.
3. Ask the customer to approve
Build the verification page in your existing signed-in application. If necessary, sign the customer in and return them to the same pending request.
Show the requesting CLI name, the account or organization, and the access being granted. Offer Approve and Deny. Resolve identity from the authenticated browser session and verify membership before allowing an organization choice. Do not trust an account ID supplied by the CLI.
Require an explicit, CSRF-protected approval action. Opening the link must not approve access. On approval, record the authorized user, account, and permitted scope server-side. Only allow a pending, unexpired session to transition to approved or denied.
4. Exchange proof for a credential
Implement POST /cli-auth/status. The CLI polls with:
{
"session": "RANDOM_SESSION_ID",
"code_verifier": "ORIGINAL_VERIFIER"
}Look up the session, check expiry, and compare the verifier's base64url SHA-256 hash with the stored challenge using a constant-time comparison. A session ID or an approved browser session alone must never be enough to obtain the credential.
Return JSON with the appropriate state:
| State | Response |
|---|---|
| Waiting for approval | {"status":"pending"} |
| Customer denied access | {"status":"denied"} |
| Session timed out | {"status":"expired"} |
| Already redeemed | {"status":"consumed"} |
Once approved and proof is valid, atomically claim the session for redemption and issue a dedicated credential for the approved account and permissions. Prevent concurrent requests from issuing multiple credentials. Coordinate issuance and state changes so a failed issuance cannot leave a reusable approved request or an untracked credential.
Return HTTP 200 once with:
{
"status": "complete",
"api_key": "NEW_API_CREDENTIAL",
"key_name": "acme CLI on customer-laptop",
"org_id": "APPROVED_ORGANIZATION_ID"
}api_key is the response field even when your API uses bearer authentication. org_id is optional. The credential must be one your API already knows how to validate. Never return a provider management key or the customer's browser session cookie.
Apply Cache-Control: no-store to these responses and exclude credentials and verifiers from logs. Store API credentials according to your backend's credential model. If a one-time response is lost after redemption, require a new login; give customers a way to revoke the orphaned credential.
5. Support logout and revocation
Implement POST /cli-auth/revoke. The CLI authenticates this request with the issued credential:
POST /cli-auth/revoke HTTP/1.1
Host: api.example.com
Authorization: Bearer NEW_API_CREDENTIALThis endpoint receives a bearer header even if normal API requests use a named API-key header. Validate the credential and revoke only that credential. Return a successful HTTP status once revocation is complete.
Issue a dedicated key for each approval. If the API identity check rejects that key, the CLI attempts to revoke it and preserves the previous login. It leaves a key already saved in the current profile alone. If cleanup fails, the error includes guidance to revoke the unused key in your account. Your revocation endpoint must not invalidate other keys or browser sessions as a side effect.
logout attempts revocation and then removes local credentials, including when the request fails. Provide account-level credential management for recovery. The CLI keeps the revocation URL of the service that issued the saved credential. Changing the current approval URL does not redirect logout to another service.
6. Test the complete experience
After regenerating and building your CLI:
- Run
node dist/cli.js loginand approve with an existing account. Confirm a read operation returns that account's data. - Deny another request. Let a third expire. Neither should issue credentials.
- Try a wrong verifier and concurrent redemption requests. Neither should expose or duplicate the credential.
- Run
login --no-browserand complete approval from the printed link. - Run
logout, inspect itsrevokedresult, and confirm your API rejects the revoked credential. - Approve with an unexpected account while using
--account EXPECTED_ACCOUNT_ID. Confirm the old login still works and the newly rejected key is revoked. Simulate an unavailable revocation endpoint and check that the CLI reports recovery guidance.
Repeat with the accounts, organizations, and environments your customers use. See session storage and logout for the generated client's current limits.