Browser-Approved CLI & MCP Login
OAuth device authorization for short-lived, tenant- and environment-bound CLI/MCP credentials without pasted API keys.
- Approval
- Client, tenant, environment, scopes, and duration
- Credential
- Short-lived access with a rotating refresh family
- Exposure
No broad API key in terminal or model-visible configuration
Ductor uses an OAuth 2.0 device-style flow for interactive CLI and MCP clients.
Enablement
auth:
enabled: true
api_key_enabled: true
device_auth:
enabled: true
issuer: https://ductor.example.com
device_ttl: 10m
access_ttl: 1h
refresh_ttl: 720h
poll_interval: 5s
max_duration: 24h
allow_http: falseSupply the 32-byte-or-longer pepper through DUCTOR_DEVICE_AUTH_PEPPER; do not put
it in a committed config file. HTTPS is required except for explicitly enabled
loopback development. Migration 477 registers the public ductor-cli and
ductor-mcp clients.
User flow
ductor auth login --issuer https://ductor.example.comThe CLI opens the browser when possible and prints only the verification URL and short human code. The approval page shows requested scope and duration. Read-only is the default. Write, destructive, wildcard, or admin scopes require visible elevated approval, and the approving person must already possess every requested permission.
- Request
The CLI creates a short-lived device grant and receives a human code.
- Review
The browser displays client, tenant, environment, scopes, and duration.
- Approve
An authenticated user grants only permissions they already possess.
- Rotate
The CLI redeems once, then rotates refresh credentials on every use.
CLI and MCP gain narrow, expiring access with server-side revocation.
Human codes cannot redeem credentials, and URLs contain no secrets.
The CLI stores returned credentials in the operating-system user config directory
with owner-only permissions. ductor auth status is redacted; ductor auth revoke
revokes the server-side refresh family before deleting the local file.
Protocol endpoints
POST /oauth/device/authorizationPOST /oauth/tokenPOST /oauth/revokeGET /oauth/device- authenticated
POST /oauth/device/verify,/approve, and/deny GET /.well-known/oauth-authorization-server
Protocol POST bodies are form-encoded. Polling returns the standard
authorization_pending, slow_down, access_denied, expired_token, and
invalid_grant results. An early poll durably increases the interval by five
seconds.
Security properties
- Device and refresh secrets are 256-bit random values; Postgres stores only SHA-256 digests. The human code uses HMAC-SHA-256 with the deployment pepper.
- A SameSite=Strict, HttpOnly browser binding ties approval to the initiating user and tenant. A stolen human code alone cannot poll or redeem.
- The URL contains no secret or code. Responses use
no-storeandno-referrer. - Completion inserts the access key and refresh family and consumes the grant in one transaction. Plaintext credentials are returned once.
- Refresh rotates every use. Reuse of an old token atomically revokes the entire family and current access credential.
- Client disablement revokes its families immediately. MCP remains request-stateless after login and still authorizes every tool call.
Approved duration is a hard ceiling
Refresh cannot extend consent past the duration shown in the browser. Individual access credentials use the shorter of the configured access TTL and the remaining approved window.
Related
Authentication
Every credential type Ductor accepts — OIDC/JWT, DB-backed API keys, SAML browser sessions, and internal service tokens — and how to configure each.
API Keys
Mint, list, and revoke DB-backed tenant API keys through the v2 API — roles, scopes, environment scope, expiry, storage, and rotation.