A CLI tool for authenticating with AuthGate server via OAuth 2.0 Device Authorization Flow (RFC 8628). Designed for headless environments, SSH sessions, and scenarios where browser-redirect flows are impractical.
Key capabilities: token storage with multi-client support · automatic token refresh · RFC 8628-compliant polling with exponential backoff · TLS 1.2+ enforcement
- AuthGate CLI - OAuth 2.0 Device Flow Client
- Go 1.25+ — required to build from source
- AuthGate server — must be running and accessible (AuthGate Documentation)
- Default server address:
http://localhost:8080
# 1. Clone and build
git clone <repository-url>
cd device-cli
make build
# 2. Get your Client ID from the AuthGate server startup logs:
# Client ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# 3. Run
./bin/device-cli -client-id=<your-client-id>Priority order: Flag > Environment Variable > .env file > default
| Parameter | Flag | Environment Variable | Default |
|---|---|---|---|
| Client ID | -client-id |
CLIENT_ID |
(required) |
| Server URL | -server-url |
SERVER_URL |
http://localhost:8080 |
| Token File | -token-file |
TOKEN_FILE |
.authgate-tokens.json |
| Token Store | -token-store |
TOKEN_STORE |
auto |
Token Store modes:
auto(default): Tries OS keyring first, falls back to file if keyring is unavailablekeyring: Uses OS keyring only (macOS Keychain, GNOME Keyring, Windows Credential Manager)file: Uses JSON file only
Example .env file:
CLIENT_ID=abc-123
SERVER_URL=http://localhost:8080
TOKEN_FILE=.authgate-tokens.json
TOKEN_STORE=auto./bin/device-cli -h # view all optionssequenceDiagram
participant CLI as AuthGate CLI
participant Server as AuthGate Server
participant Browser as User's Browser
CLI->>Server: POST /oauth/device/code (client_id)
Server-->>CLI: device_code, user_code, verification_uri
CLI->>CLI: Display verification_uri + user_code
Browser->>Server: User visits verification_uri
Browser->>Server: User enters user_code + logs in
Server-->>Browser: Authorization granted
loop Polling every 5s (with backoff on slow_down)
CLI->>Server: POST /oauth/token (device_code)
Server-->>CLI: authorization_pending / slow_down / tokens
end
Server-->>CLI: access_token + refresh_token
CLI->>CLI: Save tokens to .authgate-tokens.json (0600)
note over CLI,Server: On subsequent runs
CLI->>Server: Verify access_token
alt Token valid
Server-->>CLI: 200 OK
else Token expired
CLI->>Server: POST /oauth/token (grant_type=refresh_token)
Server-->>CLI: New access_token + refresh_token
CLI->>CLI: Update token file atomically
end
-
Run the tool with your client ID
-
The CLI displays a verification URL and user code:
Please open this link to authorize: http://localhost:8080/device?user_code=ABC12345 Or manually visit: http://localhost:8080/device And enter code: ABC12345
-
Open the URL in your browser
-
Log in to AuthGate (default:
admin/ check server logs for password) -
Enter the user code when prompted
-
The CLI detects authorization automatically and saves your tokens
Tokens are saved locally after first login. The CLI will:
- Reuse valid access tokens
- Automatically refresh expired access tokens using the refresh token
- Start a new device flow only if refresh fails
Tokens are managed by the credstore package from sdk-go, which supports multiple storage backends:
| Backend | Description |
|---|---|
auto |
Tries OS keyring first, falls back to file (default) |
keyring |
OS keyring: macOS Keychain, GNOME Keyring, Windows Credential Manager |
file |
JSON file at .authgate-tokens.json (configurable) |
A single store supports multiple Client IDs.
When using file-based storage, the JSON format is:
{
"tokens": {
"client-id-1": {
"access_token": "...",
"refresh_token": "...",
"token_type": "Bearer",
"expires_at": "2026-01-20T12:00:00Z",
"client_id": "client-id-1"
},
"client-id-2": {
"access_token": "...",
"refresh_token": "...",
"token_type": "Bearer",
"expires_at": "2026-01-20T13:00:00Z",
"client_id": "client-id-2"
}
}
}Security properties:
- File-based storage: created with
0600permissions (owner read/write only) - Keyring storage: uses OS-level credential encryption
- Automatic fallback warning when OS keyring is unavailable
Never commit token files to version control. Add
.authgate-tokens.jsonto.gitignore.
# First run — prompts for browser authorization
./bin/device-cli -client-id=abc-123
# Subsequent runs — reuses saved tokens automatically
./bin/device-cli -client-id=abc-123
# Custom server URL
./bin/device-cli -client-id=abc-123 -server-url=https://auth.example.com
# Use OS keyring for token storage
./bin/device-cli -client-id=abc-123 -token-store=keyring
# Use file-only storage
./bin/device-cli -client-id=abc-123 -token-store=file
# Multiple clients — stored in same backend by default
./bin/device-cli -client-id=abc-123
./bin/device-cli -client-id=xyz-789
# Multiple clients — separate token files (file mode)
./bin/device-cli -client-id=abc-123 -token-store=file -token-file=./work-tokens.json
./bin/device-cli -client-id=xyz-789 -token-store=file -token-file=./personal-tokens.jsonThe CLI handles all OAuth 2.0 Device Authorization Grant error codes defined in RFC 8628:
| Error | Meaning | CLI Behaviour |
|---|---|---|
authorization_pending |
User has not authorized yet | Continues polling, shows progress dots |
slow_down |
Server requests slower polling | Adds 5s to polling interval per RFC 8628 §3.5 (max 60s) |
expired_token |
Device code expired (default: 30 minutes) | Stops polling, prompts to restart authentication |
access_denied |
User explicitly denied authorization | Stops and displays denial message |
| Other errors | Unexpected server errors | Stops and displays detailed error information |
- Initial interval: 5 seconds (set by server)
- TUI progress: interactive terminal UI when running in a TTY; plain text output otherwise
slow_downbackoff: adds 5 seconds per signal (per RFC 8628 §3.5), capped at 60s
Initial: 5s
1st slow_down: 10s
2nd slow_down: 15s
3rd slow_down: 20s
...
Maximum: 60s- All operations respect Go context cancellation
- Graceful shutdown on
Ctrl+C - Per-operation timeouts: device code 10s, token exchange 5s, verification 10s, refresh 10s
| Protection | Detail |
|---|---|
| Request timeout | Per-operation: 5–10 seconds (see Context section) |
| Minimum TLS version | TLS 1.2 |
| HTTP warning | Warns automatically when server URL uses plain HTTP |
| Connection pooling | Idle connection limits to manage resources |
SERVER_URL: Validates URL format and scheme (http/httpsonly)CLIENT_ID: Warns if value is not a valid UUID format
- Keyring (default when available): OS-level credential encryption
- File fallback:
0600permissions (owner-only) - CLI warns when OS keyring is unavailable and file storage is used
- All errors are checked — no silent failures
- Error chains preserve full context for debugging
- Token values are truncated or redacted in log output
- Add
.authgate-tokens.jsonto.gitignore - Use HTTPS URLs in production — the CLI will warn if you don't
- Delete token files when no longer needed
- Review any security warnings printed at startup
CLIENT_ID not set
Provide via -client-id=<id> flag, CLIENT_ID env var, or .env file. Find your ID in the AuthGate server startup logs.
connection refused
Start the AuthGate server in another terminal:
./bin/authgate serverVerify the server URL matches (default: http://localhost:8080).
token verification failed
The token was revoked or is invalid. Delete the token file and re-authenticate:
rm .authgate-tokens.json
./bin/device-cli -client-id=<your-id>refresh failed
The CLI will automatically start a new device flow. Follow the browser authorization steps again.
Polling is slowing down
Normal behavior — the server returned a slow_down signal. The CLI has automatically increased its polling interval. See Error Reference.
context deadline exceeded
A request timed out. Check your network connection and server availability.
# Run tests with coverage
make test
# Build binary (output: bin/device-cli)
make build
# Lint and format
make lint
make fmt
# Cross-platform builds
make build_linux_amd64
make build_linux_arm64
# Clean build artifacts
make cleanFor issues or questions, please open an issue on the project repository.