From ca95d12a1e61485f6fda487ebabd7fbf32b62a3c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 26 Apr 2026 14:41:50 +0800 Subject: [PATCH 1/5] docs(readme): sync configuration and subcommand reference with implementation - Correct SCOPE default to email profile in env vars and .env.example - Document timeout and response-body-size environment variables and flags - Update token lifecycle to reference OIDC Discovery and parallel UserInfo - Fix device authorization endpoint reference to /oauth/device/code - Add Token Subcommands section covering get, inspect, decode, and delete - Note that --version is also exposed as a version subcommand --- .env.example | 2 +- README.md | 89 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index cfd7ce6..436510f 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ SERVER_URL=http://localhost:8080 CLIENT_ID= # CLIENT_SECRET= # Leave empty for public PKCE client (recommended for CLIs) CALLBACK_PORT=8888 -SCOPE=read write +SCOPE=email profile TOKEN_FILE=.authgate-tokens.json # Caller-supplied extra JWT claims are attached via --extra-claims key=value diff --git a/README.md b/README.md index 608790e..afc05bd 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ This mirrors the authentication strategy used by **GitHub CLI**, **Azure CLI**, - [Configuration](#configuration) - [Authentication Flows](#authentication-flows) - [Token Storage](#token-storage) +- [Token Subcommands](#token-subcommands) - [Troubleshooting](#troubleshooting) - [Development](#development) @@ -144,7 +145,7 @@ sequenceDiagram participant D as User's Other Device participant S as AuthGate Server - CLI->>S: POST /oauth/device_authorization + CLI->>S: POST /oauth/device/code S-->>CLI: device_code + user_code + verification_uri + interval CLI->>U: display verification_uri + user_code U->>D: visit verification_uri, enter user_code @@ -168,11 +169,14 @@ The CLI chooses between these flows automatically based on: the `--device` flag, On each run the CLI follows this order: -1. **Load cached tokens** — read from `TOKEN_FILE` keyed by `CLIENT_ID` -2. **Valid access token** — use it directly, skip authentication -3. **Expired access token** — attempt a silent refresh with the refresh token -4. **Expired/missing refresh token** — trigger full re-authentication (browser or device flow) -5. **After any successful auth** — verify token at `/oauth/tokeninfo`, then demonstrate auto-refresh on `401` +1. **Resolve endpoints** — fetch OIDC Discovery (`/.well-known/openid-configuration`); fall back to hardcoded paths if Discovery fails +2. **Load cached tokens** — read from the configured store (keyring or file) keyed by `CLIENT_ID` +3. **Valid access token** — use it directly, skip authentication +4. **Expired access token** — attempt a silent refresh with the refresh token +5. **Expired/missing refresh token** — trigger full re-authentication (browser or device flow) +6. **After any successful auth** — verify the token at `/oauth/tokeninfo` and fetch `/oauth/userinfo` in parallel, then demonstrate auto-refresh on `401` + +The process responds to `SIGINT` / `SIGTERM` and cancels the in-flight flow cleanly. --- @@ -278,10 +282,26 @@ Configuration is resolved in priority order: **CLI flag → environment variable | `CLIENT_SECRET` | _(empty)_ | Client secret — omit for public/PKCE clients | | `CALLBACK_PORT` | `8888` | Local port for the redirect callback server | | `REDIRECT_URI` | _(auto-computed)_ | Override computed redirect URI | -| `SCOPE` | `read write` | Space-separated OAuth scopes | +| `SCOPE` | `email profile` | Space-separated OAuth scopes | | `TOKEN_FILE` | `.authgate-tokens.json` | Path to the token cache file | | `TOKEN_STORE` | `auto` | Storage backend: `auto`, `file`, or `keyring` | +#### Timeouts and limits + +Every network call has its own configurable timeout. Values are parsed with `time.ParseDuration` (e.g. `10s`, `2m`, `1m30s`); invalid or non-positive values fall back to the default (with a warning), and any value above 10 minutes is capped. + +| Variable | Default | Description | +| ---------------------------- | ------- | --------------------------------------------------------------------------------- | +| `TOKEN_EXCHANGE_TIMEOUT` | `10s` | Authorization-code → token exchange | +| `TOKEN_VERIFICATION_TIMEOUT` | `10s` | `/oauth/tokeninfo` request | +| `REFRESH_TOKEN_TIMEOUT` | `10s` | Refresh token grant | +| `DEVICE_CODE_REQUEST_TIMEOUT`| `10s` | Device authorization request | +| `CALLBACK_TIMEOUT` | `2m` | How long the local callback server waits for the browser before falling back | +| `USERINFO_TIMEOUT` | `10s` | `/oauth/userinfo` request | +| `DISCOVERY_TIMEOUT` | `10s` | OIDC Discovery fetch | +| `REVOCATION_TIMEOUT` | `10s` | RFC 7009 revocation request issued by `token delete` | +| `MAX_RESPONSE_BODY_SIZE` | `1048576` (1 MiB) | Bytes read from any OAuth response; capped at 100 MiB | + > Extra JWT claims are configured via the `--extra-claims` / `--extra-claims-file` flags (see below). They have no environment-variable equivalent because each claim is its own key=value entry. ### CLI flags @@ -299,7 +319,9 @@ Configuration is resolved in priority order: **CLI flag → environment variable | `--device` | — | Force Device Code Flow | | `--extra-claims` | — | Caller-supplied JWT claim as `key=value` (repeatable) | | `--extra-claims-file` | — | Path to a `.env`-style file (one `key=value` per line) | -| `--version` | — | Print version and exit | +| `--version` | — | Print version and exit (also available as `version` subcommand) | + +Each timeout / size environment variable also has a matching flag with the same name in lower-kebab-case (e.g. `--callback-timeout`, `--max-response-body-size`). ### Usage examples @@ -316,9 +338,18 @@ Configuration is resolved in priority order: **CLI flag → environment variable # Use a non-default callback port ./bin/authgate-cli --port 9999 +# Print the stored access token (raw, or as JSON with --json) +./bin/authgate-cli token get + # Inspect what the server knows about the stored access token ./bin/authgate-cli token inspect +# Decode the stored JWT locally without contacting the server +./bin/authgate-cli token decode --field sub + +# Revoke and remove the stored token +./bin/authgate-cli token delete + # Attach caller-supplied JWT claims (sent on every token grant + refresh) ./bin/authgate-cli \ --extra-claims project=acme-prod \ @@ -453,6 +484,48 @@ The `flow` field records whether `browser` or `device` was used. --- +## Token Subcommands + +`authgate-cli token` exposes four utility commands for managing the credentials cached by the main flow. They share the global flags (`--client-id`, `--token-file`, `--token-store`, …); commands that need network access additionally honour `--server-url` and the timeout flags. + +### `token get` — print the stored access token + +```bash +./bin/authgate-cli token get # prints the access token (raw) +./bin/authgate-cli token get --json # prints access_token, token_type, expires_at, expired, client_id +``` + +The refresh token is never printed (it is a long-lived secret). Exits non-zero with a hint if no token is stored. + +### `token inspect` — server-side introspection + +```bash +./bin/authgate-cli token inspect +``` + +POSTs the stored access token to `/oauth/tokeninfo` and pretty-prints the JSON response. Useful when the local cache disagrees with the server. + +### `token decode` — local JWT decode + +```bash +./bin/authgate-cli token decode # full claims map +./bin/authgate-cli token decode --field aud # single claim (string values printed raw, others as JSON) +./bin/authgate-cli token decode -f sub +``` + +Base64-decodes the JWT payload locally **without verifying the signature**. Fails with a clear error if the token is opaque (not a JWT). + +### `token delete` — revoke and remove + +```bash +./bin/authgate-cli token delete # revokes on the server (RFC 7009), then deletes locally +./bin/authgate-cli token delete --local-only # skip server revocation +``` + +By default both access and refresh tokens are revoked concurrently against `/oauth/revoke`. If revocation fails (network down, server unreachable) the local token is still deleted — graceful degradation. Use `--local-only` to skip the network round-trip entirely. + +--- + ## Troubleshooting ### Port 8888 is already in use From 804ff4a3576ab547636c61013ca5d13a6f220960 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 5 May 2026 18:07:03 +0800 Subject: [PATCH 2/5] docs(readme): correct token inspect HTTP method description - Replace inaccurate POST description with HTTP GET and Authorization Bearer header for token inspect --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index afc05bd..b6a664f 100644 --- a/README.md +++ b/README.md @@ -503,7 +503,7 @@ The refresh token is never printed (it is a long-lived secret). Exits non-zero w ./bin/authgate-cli token inspect ``` -POSTs the stored access token to `/oauth/tokeninfo` and pretty-prints the JSON response. Useful when the local cache disagrees with the server. +Sends an HTTP GET request to `/oauth/tokeninfo` with the stored access token in the `Authorization: Bearer ...` header and pretty-prints the JSON response. Useful when the local cache disagrees with the server. ### `token decode` — local JWT decode From bab2afe417db46dda5d89c20ada416152d550d16 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 5 May 2026 18:14:10 +0800 Subject: [PATCH 3/5] docs(readme): clarify OIDC discovery, timeout scope, and body-size limit - Note UserInfo and revocation endpoints come from OIDC Discovery and treat the hardcoded paths as defaults - Clarify that the configurable timeouts cover OAuth flow steps and that the auto-refresh demo reuses the parent context - Tighten MAX_RESPONSE_BODY_SIZE description to reflect parsed responses and call out the fixed 4 KiB revocation drain --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b6a664f..178d6e4 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ On each run the CLI follows this order: 3. **Valid access token** — use it directly, skip authentication 4. **Expired access token** — attempt a silent refresh with the refresh token 5. **Expired/missing refresh token** — trigger full re-authentication (browser or device flow) -6. **After any successful auth** — verify the token at `/oauth/tokeninfo` and fetch `/oauth/userinfo` in parallel, then demonstrate auto-refresh on `401` +6. **After any successful auth** — verify the token at `/oauth/tokeninfo` and fetch UserInfo from the resolved UserInfo endpoint (default: `/oauth/userinfo`) in parallel, then demonstrate auto-refresh on `401` The process responds to `SIGINT` / `SIGTERM` and cancels the in-flight flow cleanly. @@ -288,7 +288,7 @@ Configuration is resolved in priority order: **CLI flag → environment variable #### Timeouts and limits -Every network call has its own configurable timeout. Values are parsed with `time.ParseDuration` (e.g. `10s`, `2m`, `1m30s`); invalid or non-positive values fall back to the default (with a warning), and any value above 10 minutes is capped. +Each OAuth flow step listed below has its own configurable timeout. Values are parsed with `time.ParseDuration` (e.g. `10s`, `2m`, `1m30s`); invalid or non-positive values fall back to the default (with a warning), and any value above 10 minutes is capped. The post-auth auto-refresh demo (`makeAPICallWithAutoRefresh`) reuses the parent context and is bounded by `SIGINT` / `SIGTERM` rather than a dedicated timeout. | Variable | Default | Description | | ---------------------------- | ------- | --------------------------------------------------------------------------------- | @@ -300,7 +300,7 @@ Every network call has its own configurable timeout. Values are parsed with `tim | `USERINFO_TIMEOUT` | `10s` | `/oauth/userinfo` request | | `DISCOVERY_TIMEOUT` | `10s` | OIDC Discovery fetch | | `REVOCATION_TIMEOUT` | `10s` | RFC 7009 revocation request issued by `token delete` | -| `MAX_RESPONSE_BODY_SIZE` | `1048576` (1 MiB) | Bytes read from any OAuth response; capped at 100 MiB | +| `MAX_RESPONSE_BODY_SIZE` | `1048576` (1 MiB) | Bytes read from OAuth responses the CLI parses or prints (token, tokeninfo, userinfo, device-flow); revocation drains a fixed 4 KiB and is unaffected. Capped at 100 MiB | > Extra JWT claims are configured via the `--extra-claims` / `--extra-claims-file` flags (see below). They have no environment-variable equivalent because each claim is its own key=value entry. @@ -522,7 +522,7 @@ Base64-decodes the JWT payload locally **without verifying the signature**. Fail ./bin/authgate-cli token delete --local-only # skip server revocation ``` -By default both access and refresh tokens are revoked concurrently against `/oauth/revoke`. If revocation fails (network down, server unreachable) the local token is still deleted — graceful degradation. Use `--local-only` to skip the network round-trip entirely. +By default both access and refresh tokens are revoked concurrently against the resolved revocation endpoint (default: `/oauth/revoke`). If revocation fails (network down, server unreachable) the local token is still deleted — graceful degradation. Use `--local-only` to skip the network round-trip entirely. --- From 9e09b98b800da68d9807cff6a4d40575d661b925 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 5 May 2026 18:19:33 +0800 Subject: [PATCH 4/5] docs(readme): clarify revocation conditional and body-size parsing - Note refresh token revocation only runs when a refresh token is present - Distinguish duration parsing from MAX_RESPONSE_BODY_SIZE byte parsing in the timeouts intro --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 178d6e4..c72481f 100644 --- a/README.md +++ b/README.md @@ -288,7 +288,7 @@ Configuration is resolved in priority order: **CLI flag → environment variable #### Timeouts and limits -Each OAuth flow step listed below has its own configurable timeout. Values are parsed with `time.ParseDuration` (e.g. `10s`, `2m`, `1m30s`); invalid or non-positive values fall back to the default (with a warning), and any value above 10 minutes is capped. The post-auth auto-refresh demo (`makeAPICallWithAutoRefresh`) reuses the parent context and is bounded by `SIGINT` / `SIGTERM` rather than a dedicated timeout. +Each OAuth flow step listed below has its own configurable timeout. Timeout values (the `*_TIMEOUT` variables) are parsed with `time.ParseDuration` (e.g. `10s`, `2m`, `1m30s`); invalid or non-positive values fall back to the default (with a warning), and any value above 10 minutes is capped. `MAX_RESPONSE_BODY_SIZE` is parsed as an integer byte count (not a duration) and is capped separately at 100 MiB. The post-auth auto-refresh demo (`makeAPICallWithAutoRefresh`) reuses the parent context and is bounded by `SIGINT` / `SIGTERM` rather than a dedicated timeout. | Variable | Default | Description | | ---------------------------- | ------- | --------------------------------------------------------------------------------- | @@ -522,7 +522,7 @@ Base64-decodes the JWT payload locally **without verifying the signature**. Fail ./bin/authgate-cli token delete --local-only # skip server revocation ``` -By default both access and refresh tokens are revoked concurrently against the resolved revocation endpoint (default: `/oauth/revoke`). If revocation fails (network down, server unreachable) the local token is still deleted — graceful degradation. Use `--local-only` to skip the network round-trip entirely. +By default the access token, and the refresh token if present, are revoked concurrently against the resolved revocation endpoint (default: `/oauth/revoke`). If revocation fails (network down, server unreachable) the local token is still deleted — graceful degradation. Use `--local-only` to skip the network round-trip entirely. --- From a53e5ec61553b7aae2747a765aca79a13285e0c9 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 5 May 2026 18:25:14 +0800 Subject: [PATCH 5/5] docs(readme): clarify token verification runs for cached tokens too - Reword the post-auth step to note verification and UserInfo fetch run after any selected token, not only after interactive auth --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c72481f..27217c3 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ On each run the CLI follows this order: 3. **Valid access token** — use it directly, skip authentication 4. **Expired access token** — attempt a silent refresh with the refresh token 5. **Expired/missing refresh token** — trigger full re-authentication (browser or device flow) -6. **After any successful auth** — verify the token at `/oauth/tokeninfo` and fetch UserInfo from the resolved UserInfo endpoint (default: `/oauth/userinfo`) in parallel, then demonstrate auto-refresh on `401` +6. **After selecting a token to use** (cached, refreshed, or newly issued) — verify the token at `/oauth/tokeninfo` and fetch UserInfo from the resolved UserInfo endpoint (default: `/oauth/userinfo`) in parallel, then demonstrate auto-refresh on `401` The process responds to `SIGINT` / `SIGTERM` and cancels the in-flight flow cleanly.