{"id":45162464,"url":"https://github.com/go-authgate/cli","last_synced_at":"2026-03-08T11:06:04.519Z","repository":{"id":339498629,"uuid":"1162190870","full_name":"go-authgate/cli","owner":"go-authgate","description":"A CLI example that combines Authorization Code Flow (browser-based) and Device Code Flow into a single binary. The flow is selected automatically based on the environment — no configuration required.","archived":false,"fork":false,"pushed_at":"2026-03-01T14:16:34.000Z","size":202,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-01T17:56:59.819Z","etag":null,"topics":["authorization","authorization-code-grant","command-line"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/go-authgate.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":["https://www.paypal.me/appleboy46"]}},"created_at":"2026-02-20T00:56:07.000Z","updated_at":"2026-03-01T14:10:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/go-authgate/cli","commit_stats":null,"previous_names":["go-authgate/cli"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/go-authgate/cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-authgate%2Fcli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-authgate%2Fcli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-authgate%2Fcli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-authgate%2Fcli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/go-authgate","download_url":"https://codeload.github.com/go-authgate/cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-authgate%2Fcli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30253944,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-08T08:59:44.879Z","status":"ssl_error","status_checked_at":"2026-03-08T08:58:02.867Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorization","authorization-code-grant","command-line"],"created_at":"2026-02-20T06:03:43.175Z","updated_at":"2026-03-08T11:06:04.480Z","avatar_url":"https://github.com/go-authgate.png","language":"Go","funding_links":["https://www.paypal.me/appleboy46"],"categories":[],"sub_categories":[],"readme":"# Authgate CLI\n\n[![Lint and Testing](https://github.com/go-authgate/cli/actions/workflows/testing.yml/badge.svg)](https://github.com/go-authgate/cli/actions/workflows/testing.yml)\n[![Trivy Security Scan](https://github.com/go-authgate/cli/actions/workflows/security.yml/badge.svg)](https://github.com/go-authgate/cli/actions/workflows/security.yml)\n\n**A production-ready OAuth 2.0 CLI authentication library for Go.**\n\nBuilding OAuth 2.0 into a CLI tool means solving the same problems every time: detecting whether a browser is available, implementing PKCE correctly, running a local callback server, caching tokens, handling refresh, and gracefully falling back to a headless device flow for SSH/CI environments. Authgate CLI handles all of this so you can focus on your application logic.\n\nThis mirrors the authentication strategy used by **GitHub CLI**, **Azure CLI**, and **Google Cloud SDK** — automatically selecting between Authorization Code Flow with PKCE (browser) and Device Authorization Grant (headless/SSH) based on the runtime environment, with no manual configuration required.\n\n---\n\n## Table of Contents\n\n- [Why This CLI?](#why-this-cli)\n- [Quick Start](#quick-start)\n- [How It Works](#how-it-works)\n- [Interactive Terminal UI](#interactive-terminal-ui)\n- [Configuration](#configuration)\n- [Authentication Flows](#authentication-flows)\n- [Token Storage](#token-storage)\n- [Troubleshooting](#troubleshooting)\n- [Development](#development)\n\n---\n\n## Why This CLI?\n\nWithout Authgate CLI, every OAuth-enabled CLI tool must implement the same boilerplate:\n\n| If you implement it yourself                                | Authgate CLI handles it for you                         |\n| ----------------------------------------------------------- | ------------------------------------------------------- |\n| Detect SSH session / headless environment                   | ✅ Auto-selects PKCE or Device Flow                     |\n| Generate PKCE `code_verifier` + `code_challenge` (RFC 7636) | ✅ Built-in                                             |\n| Spin up a local callback HTTP server                        | ✅ Built-in, bound to `127.0.0.1`                       |\n| Add CSRF `state` parameter and validate on callback         | ✅ Built-in                                             |\n| Cache tokens to disk with safe file permissions             | ✅ Written as `0600`, multi-client keyed by `CLIENT_ID` |\n| Refresh access token silently on expiry                     | ✅ Built-in, with auto-retry on `401`                   |\n| Fall back to Device Flow when browser fails or times out    | ✅ Automatic                                            |\n| Handle concurrent writes to the token file                  | ✅ File-lock with stale-lock timeout                    |\n\n---\n\n## Quick Start\n\n### Prerequisites\n\n- Go 1.24+\n- A running [AuthGate server](../README.md) — get the `CLIENT_ID` UUID from its startup logs\n\n### 1. Configure\n\n```bash\ncp .env.example .env\n```\n\nEdit `.env` and set at minimum:\n\n```bash\nSERVER_URL=http://localhost:8080\nCLIENT_ID=\u003cuuid-from-server-logs\u003e   # Required — all other fields have defaults\n```\n\n### 2. Run\n\n```bash\ngo run .\n```\n\nThe CLI auto-detects your environment and selects the appropriate flow:\n\n- **Local workstation with a browser** → opens a browser tab, completes authorization silently\n- **SSH session / no display / CI** → prints a URL and user code for you to authorize from another device\n\n### 3. Build a binary\n\n```bash\nmake build\n# Binary written to bin/cli\n./bin/cli\n```\n\n---\n\n## How It Works\n\n### System architecture\n\nAuthgate CLI sits between your terminal and the AuthGate server. It acquires tokens on your behalf and demonstrates how to use them against a protected resource.\n\n```\n┌─────────────────────────────────────────────────────────┐\n│  Your Terminal                                          │\n│                                                         │\n│  ┌────────────┐   OAuth tokens   ┌──────────────────┐   │\n│  │ Authgate   │ ◄──────────────► │  AuthGate Server │   │\n│  │ CLI        │                  │  (OAuth 2.0 AS)  │   │\n│  └─────┬──────┘                  └──────────────────┘   │\n│        │                                                │\n│        │ Bearer token                                   │\n│        ▼                                                │\n│  ┌─────────────┐                                        │\n│  │  Protected  │                                        │\n│  │  Resource   │                                        │\n│  └─────────────┘                                        │\n└─────────────────────────────────────────────────────────┘\n```\n\n### Flow selection\n\nThe CLI automatically picks the right OAuth flow based on the runtime environment:\n\n```mermaid\nflowchart TD\n    classDef start   fill:#6366f1,color:#fff,stroke:#4f46e5\n    classDef pkce    fill:#22c55e,color:#fff,stroke:#16a34a\n    classDef device  fill:#f59e0b,color:#000,stroke:#d97706\n    classDef done    fill:#3b82f6,color:#fff,stroke:#2563eb\n    classDef check   fill:#f8fafc,color:#1e293b,stroke:#94a3b8\n\n    START([CLI Launch]):::start --\u003e FORCE_FLAG{--device?}:::check\n\n    FORCE_FLAG --\u003e|Yes| DEVICE([Device Code Flow]):::device\n    FORCE_FLAG --\u003e|No| SSH{SSH without display?}:::check\n\n    SSH --\u003e|Yes| DEVICE\n    SSH --\u003e|No| LINUX{Linux, no display?}:::check\n\n    LINUX --\u003e|Yes| DEVICE\n    LINUX --\u003e|No| PORT{Callback port free?}:::check\n\n    PORT --\u003e|In use| DEVICE\n    PORT --\u003e|Free| PKCE([Auth Code + PKCE]):::pkce\n\n    PKCE --\u003e BROWSER{Browser opens?}:::check\n    BROWSER --\u003e|Failed| DEVICE\n    BROWSER --\u003e|Success| CALLBACK{Callback received?}:::check\n\n    CALLBACK --\u003e|\"Timeout (2 min)\"| DEVICE\n    CALLBACK --\u003e|Received| DONE([Tokens Saved]):::done\n\n    DEVICE --\u003e DONE\n```\n\n### Token lifecycle\n\nOn each run the CLI follows this order:\n\n1. **Load cached tokens** — read from `TOKEN_FILE` keyed by `CLIENT_ID`\n2. **Valid access token** — use it directly, skip authentication\n3. **Expired access token** — attempt a silent refresh with the refresh token\n4. **Expired/missing refresh token** — trigger full re-authentication (browser or device flow)\n5. **After any successful auth** — verify token at `/oauth/tokeninfo`, then demonstrate auto-refresh on `401`\n\n---\n\n## Interactive Terminal UI\n\nAuthgate CLI features a rich **interactive Terminal User Interface (TUI)** built with [Bubble Tea](https://github.com/charmbracelet/bubbletea), providing visual feedback during OAuth authentication flows.\n\n### Features\n\nThe TUI provides:\n\n- **Visual Progress Indicators**: Step-by-step progress with animated spinners\n- **Real-time Timers**: Countdown for browser flow, elapsed time for device flow\n- **Progress Bars**: Visual representation of callback timeout\n- **Polling Status**: Live updates showing device flow polling count and intervals\n- **Backoff Warnings**: Clear notifications when server requests slower polling\n- **Clean Layout**: Bordered boxes, color-coded messages, and structured information\n\n### Browser Flow (Authorization Code + PKCE)\n\n```\n╭─────────────────────────────────────────────────╮\n│ Authorization Code Flow with PKCE               │\n├─────────────────────────────────────────────────┤\n│                                                 │\n│ ● Step 1/3: Opening browser             ✓       │\n│ ● Step 2/3: Waiting for callback        ◐       │\n│ ○ Step 3/3: Exchanging tokens                   │\n│                                                 │\n│ Time remaining: 1:23 / 2:00                     │\n│ ▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░ 48%                   │\n│                                                 │\n│ ◐ Please complete authorization in your browser │\n│                                                 │\n│ Press Ctrl+C to cancel                          │\n╰─────────────────────────────────────────────────╯\n```\n\n### Device Flow (Device Authorization Grant)\n\n```\n╭─────────────────────────────────────────────────╮\n│ Device Authorization Grant Flow                 │\n├─────────────────────────────────────────────────┤\n│                                                 │\n│ ╔═══════════════════════════════════════════╗   │\n│ ║  Device Authorization                     ║   │\n│ ║                                           ║   │\n│ ║  Visit: https://auth.example.com/device   ║   │\n│ ║         ?user_code=ABCD-EFGH              ║   │\n│ ║                                           ║   │\n│ ║  Or go to: https://auth.example.com       ║   │\n│ ║  And enter: ABCD-EFGH                     ║   │\n│ ╚═══════════════════════════════════════════╝   │\n│                                                 │\n│ ◐ Waiting for authorization... (poll #8, 5s)    │\n│                                                 │\n│ Elapsed: 0:43                                   │\n│                                                 │\n│ Press Ctrl+C to cancel                          │\n╰─────────────────────────────────────────────────╯\n```\n\n### UI Mode Selection\n\nThe CLI automatically chooses the appropriate UI mode:\n\n**Interactive TUI Mode** (default):\n\n- Normal terminal with sufficient size (≥60x20)\n- TTY detected\n- TERM environment variable set (not \"dumb\")\n\n**Simple Printf Mode** (automatic fallback):\n\n- CI environments (GitHub Actions, GitLab CI, CircleCI, etc.)\n- Output piped to file or another command\n- Terminal too small (\u003c 60 columns or \u003c 20 rows)\n- `TERM=dumb` or TERM unset\n- SSH session without display forwarding\n\n### Note on UI Selection\n\nThe CLI automatically detects the environment and selects the appropriate UI mode. No configuration or flags are needed - it just works.\n\n---\n\n## Configuration\n\nConfiguration is resolved in priority order: **CLI flag → environment variable → default**.\n\n### Environment variables\n\n| Variable        | Default                 | Description                                  |\n| --------------- | ----------------------- | -------------------------------------------- |\n| `SERVER_URL`    | `http://localhost:8080` | AuthGate server base URL                     |\n| `CLIENT_ID`     | _(required)_            | OAuth client ID (UUID from server logs)      |\n| `CLIENT_SECRET` | _(empty)_               | Client secret — omit for public/PKCE clients |\n| `CALLBACK_PORT` | `8888`                  | Local port for the redirect callback server  |\n| `SCOPE`         | `read write`            | Space-separated OAuth scopes                 |\n| `TOKEN_FILE`    | `.authgate-tokens.json` | Path to the token cache file                 |\n\n### CLI flags\n\n| Flag              | Env equivalent  | Description                               |\n| ----------------- | --------------- | ----------------------------------------- |\n| `--server-url`    | `SERVER_URL`    | AuthGate server URL                       |\n| `--client-id`     | `CLIENT_ID`     | OAuth client ID                           |\n| `--client-secret` | `CLIENT_SECRET` | Client secret (confidential clients only) |\n| `--redirect-uri`  | —               | Override computed redirect URI            |\n| `--port`          | `CALLBACK_PORT` | Local callback port                       |\n| `--scope`         | `SCOPE`         | OAuth scopes                              |\n| `--token-file`    | `TOKEN_FILE`    | Token cache file path                     |\n| `--device`        | —               | Force Device Code Flow                    |\n\n### Usage examples\n\n```bash\n# Auto-detect flow (default)\n./bin/cli\n\n# Force Device Code Flow (useful in scripts or CI)\n./bin/cli --device\n\n# Override server and client\n./bin/cli --server-url https://auth.example.com --client-id \u003cuuid\u003e\n\n# Use a non-default callback port\n./bin/cli --port 9999\n```\n\n---\n\n## Authentication Flows\n\n### Authorization Code Flow with PKCE (browser)\n\nUsed when a local browser and a free callback port are available. Suitable for developer workstations.\n\n```\n=== AuthGate CLI ===\nClient mode : public (PKCE)\nServer URL  : http://localhost:8080\nClient ID   : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n\nAuth method : Authorization Code Flow (browser)\nStep 1: Opening browser for authorization...\n\n  http://localhost:8080/oauth/authorize?...\n\nBrowser opened. Please complete authorization in your browser.\nStep 2: Waiting for callback on http://localhost:8888/callback ...\nAuthorization code received!\nStep 3: Exchanging authorization code for tokens...\nTokens saved to .authgate-tokens.json\n```\n\n**Security properties:**\n\n- PKCE (RFC 7636) — prevents authorization code interception\n- `state` parameter — CSRF protection on the callback\n- Callback server binds to `127.0.0.1` only\n- 2-minute timeout; falls back to Device Code Flow automatically\n\n### Device Authorization Grant (headless/SSH)\n\nUsed when no browser is available: SSH sessions without display forwarding, Linux servers, CI environments.\n\n```\n=== AuthGate CLI ===\nAuth method : Device Code Flow (SSH session without display forwarding)\nStep 1: Requesting device code...\n\n----------------------------------------\nPlease open this link to authorize:\nhttp://localhost:8080/device?user_code=ABC-12345\n\nOr visit : http://localhost:8080/device\nAnd enter: ABC-12345\n----------------------------------------\n\nStep 2: Waiting for authorization...............\nAuthorization successful!\nTokens saved to .authgate-tokens.json\n```\n\n**Polling behavior:**\n\n- Respects the server-specified polling interval (default 5 s)\n- Implements RFC 8628 exponential backoff on `slow_down` (up to 60 s)\n\n### Public vs. confidential clients\n\n| Mode          | `CLIENT_SECRET` | Token exchange        |\n| ------------- | --------------- | --------------------- |\n| Public (PKCE) | Not set         | Sends `code_verifier` |\n| Confidential  | Set             | Sends `client_secret` |\n\nPublic/PKCE is the recommended mode for CLI tools.\n\n---\n\n## Token Storage\n\nTokens are saved to `TOKEN_FILE` (default `.authgate-tokens.json`) and keyed by `CLIENT_ID`, so the same file can hold credentials for multiple clients.\n\n```json\n{\n  \"tokens\": {\n    \"\u003cclient-id\u003e\": {\n      \"access_token\": \"...\",\n      \"refresh_token\": \"...\",\n      \"token_type\": \"Bearer\",\n      \"expires_at\": \"2026-01-01T00:00:00Z\",\n      \"client_id\": \"\u003cclient-id\u003e\",\n      \"flow\": \"browser\"\n    }\n  }\n}\n```\n\nThe `flow` field records whether `browser` or `device` was used.\n\n**Concurrent write safety:** token writes use a `.lock` file with a 30-second stale-lock timeout, ensuring multiple processes can share the same token file without corruption.\n\n**File permissions:** written as `0600` (owner read/write only).\n\n---\n\n## Troubleshooting\n\n### Port 8888 is already in use\n\nThe callback server cannot start, so the CLI falls back to Device Code Flow automatically. To use a different port for PKCE:\n\n```bash\n./bin/cli --port 9999\n# or\nCALLBACK_PORT=9999 ./bin/cli\n```\n\n### Browser does not open automatically\n\nThe authorization URL is always printed to the terminal. Copy and paste it into a browser manually. The CLI will continue waiting for the callback.\n\nIf you are in a headless environment (SSH without display forwarding), use `--device` to skip the browser flow entirely:\n\n```bash\n./bin/cli --device\n```\n\n### \"CLIENT_ID is required\" error\n\nThe `CLIENT_ID` must be the UUID shown in the AuthGate server startup logs. It is not a value you create — it is assigned by the server when a client is registered.\n\n```bash\n# Check your .env\ncat .env | grep CLIENT_ID\n\n# Or pass it directly\n./bin/cli --client-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n```\n\n### Token refresh fails / kept asking to re-authenticate\n\nIf the refresh token has expired, the CLI triggers a full re-authentication. Delete the token cache to start fresh:\n\n```bash\nrm .authgate-tokens.json\n./bin/cli\n```\n\n### Authorization timeout after 2 minutes\n\nThe PKCE callback server waits up to 2 minutes for you to complete the browser flow. If it times out, the CLI falls back to Device Code Flow automatically. No action required.\n\n---\n\n## Development\n\n### Prerequisites\n\n```bash\n# Install development tools\nmake install-golangci-lint   # linter\nmake install-templ           # template code generator (if used)\n```\n\n### Common commands\n\n```bash\nmake build          # Build binary → bin/cli\nmake test           # Run tests with coverage\nmake coverage       # Open coverage report in browser\nmake lint           # Run golangci-lint\nmake fmt            # Format code\nmake dev            # Hot reload with air\nmake clean          # Remove bin/, release/, coverage.txt\nmake rebuild        # clean + build\n```\n\n### Cross-platform builds\n\n```bash\nmake build_linux_amd64    # Linux x86-64 → release/linux/amd64/cli\nmake build_linux_arm64    # Linux ARM64  → release/linux/arm64/cli\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgo-authgate%2Fcli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgo-authgate%2Fcli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgo-authgate%2Fcli/lists"}