{"content":"# aux4/oauth\n\nA provider-agnostic OAuth2 client engine for the command line. It runs the browser-based authorization-code-with-PKCE flow against any OAuth2 / OIDC provider, identified only by its endpoint URLs and your client credentials. The engine knows no third-party provider names — a provider is just a free-form --provider label that also becomes the key under which the token is stored.\n\nThe interactive CLI commands (login, token, status, logout) delegate the full flow, token storage, and refresh to aux4/curl, so there is no new credential handling there — only endpoint resolution.\n\nFor server-side web applications, two additional headless primitives — authorize-url and exchange — implement the authorization-code-with-PKCE flow as plain JSON-producing commands (no browser, no local callback server). They use the same endpoint resolution and are intended to be shelled out to by a web app's sign-in and callback handlers.\n\n## Installation\n\n``bash\naux4 aux4 pkger install aux4/oauth\n`\n\nThis also installs the aux4/curl and aux4/config dependencies.\n\n## Quick Start\n\nLog in to the built-in aux4 provider — you supply only your client credentials, and the package supplies the endpoint URLs:\n\n`bash\naux4 oauth login --provider aux4 --clientId YOUR_CLIENT_ID --clientSecret YOUR_CLIENT_SECRET\n`\n\nLog in to any other provider by passing its endpoints:\n\n`bash\naux4 oauth login --provider acme \\\n --authUrl https://acme.example/oauth/authorize \\\n --tokenUrl https://acme.example/oauth/token \\\n --scopes openid,email \\\n --clientId YOUR_CLIENT_ID --clientSecret YOUR_CLIENT_SECRET\n`\n\nAfter login, use the stored token in scripts:\n\n`bash\nTOKEN=$(aux4 oauth token --provider aux4)\ncurl -H \"Authorization: Bearer $TOKEN\" https://api.aux4.io/me\n`\n\n## Endpoint Resolution\n\nEvery login resolves endpoint URLs, scopes, and credentials with this precedence:\n\n1. **Explicit flag** — --authUrl, --tokenUrl, --userinfoUrl, --scopes, --clientId, --clientSecret. Flags always win.\n2. **User config** — a config.yaml passed via --configFile, keyed by provider name. Uses aux4/config.\n3. **Bundled config** — endpoints shipped with the package (only the aux4 provider; URLs only).\n\nIf, after resolution, authUrl or tokenUrl is empty, login fails with exit code 1 and a message telling you to pass the URL flags or install a provider package.\n\n### Built-in aux4 provider\n\nThe package bundles exactly one first-party provider, aux4, with the endpoints of its own identity provider:\n\n| Field | Value |\n|-------|-------|\n| authUrl | https://sso.aux4.io/authorize |\n| tokenUrl | https://sso.aux4.io/token |\n| userinfoUrl | https://sso.aux4.io/userinfo |\n| scopes | openid,email,profile |\n\nIt bundles **URLs only**. The clientId and clientSecret are per-app and always supplied by you — they are never bundled. For any provider name other than aux4, the bundled config has nothing, so you must pass --authUrl/--tokenUrl (or install a provider package, see below).\n\n### User config\n\nTo avoid repeating endpoints, put per-provider settings in a config.yaml and pass it with --configFile. Keys are the provider name:\n\n`yaml\nconfig:\n acme:\n authUrl: https://acme.example/oauth/authorize\n tokenUrl: https://acme.example/oauth/token\n scopes: openid,email\n clientId: secret://1password/Work/acme/clientId\n clientSecret: secret://1password/Work/acme/clientSecret\n`\n\n`bash\naux4 oauth login --provider acme --configFile config.yaml\n`\n\nExplicit flags still override any value found in the config file.\n\n**Note:** Never hardcode clientId/clientSecret in scripts. Pass them as flags, the OAUTH_CLIENT_ID / OAUTH_CLIENT_SECRET environment variables, or secret:// references resolved from a configured secret provider.\n\n### Third-party provider packages\n\nBecause the engine is provider-agnostic, third-party providers are not bundled. A community provider package supplies a thin wrapper that calls aux4 oauth login with that provider's endpoints, leaving the user to supply credentials. For example, a community/oauth-google package would depend on aux4/oauth and define a command like:\n\n`bash\naux4 oauth login --provider google \\\n --authUrl https://accounts.google.com/o/oauth2/v2/auth \\\n --tokenUrl https://oauth2.googleapis.com/token \\\n --scopes openid,email,profile \\\n --clientId \"$CLIENT_ID\" --clientSecret \"$CLIENT_SECRET\"\n`\n\nNo per-provider code lives in aux4/oauth — provider packages contribute only URLs.\n\n## Commands\n\n### oauth login\n\nAuthenticate with a provider and store the token in .oauth/<provider>.json. The provider's endpoints, scopes, and credentials are resolved as described in [Endpoint Resolution](#endpoint-resolution).\n\n`bash\naux4 oauth login --provider acme --authUrl https://acme.example/oauth/authorize --tokenUrl https://acme.example/oauth/token --clientId abc --clientSecret xyz\n`\n\nOptions:\n\n- --provider — Provider name / token-file key (required).\n- --authUrl — Authorization endpoint URL.\n- --tokenUrl — Token exchange endpoint URL.\n- --userinfoUrl — Userinfo endpoint URL (resolved; reserved for the web-login flow).\n- --scopes — Comma-separated scopes.\n- --clientId — OAuth client ID. Also read from OAUTH_CLIENT_ID or user config (including secret:// references).\n- --clientSecret — OAuth client secret. Also read from OAUTH_CLIENT_SECRET or user config (secret://).\n- --callbackPort — Local callback server port (default: 9876).\n- --tokenFile — Custom token file path (default: .oauth/<provider>.json).\n- --configFile — Path to a user config.yaml with per-provider settings.\n\n### oauth token\n\nPrint a valid access token, refreshing it automatically if it has expired.\n\n`bash\naux4 oauth token --provider aux4\n`\n\n### oauth status\n\nShow the token status (valid/expired), scopes, expiry, and token file path.\n\n`bash\naux4 oauth status --provider aux4\n`\n\n### oauth logout\n\nRemove the stored token for the provider.\n\n`bash\naux4 oauth logout --provider aux4\n`\n\n### oauth authorize-url\n\nBuild a PKCE authorization URL for the server-side web login flow. This is a headless primitive — it generates the PKCE values and prints a JSON object; it does not open a browser or run a local server. Resolve authUrl and scopes with the usual precedence (flag > user config > bundled).\n\n`bash\naux4 oauth authorize-url --provider aux4 \\\n --clientId YOUR_CLIENT_ID \\\n --redirectUri https://app.example/auth/callback\n`\n\nOutput:\n\n`json\n{\n \"url\": \"https://sso.aux4.io/authorize?response_type=code&client_id=...&code_challenge=...&code_challenge_method=S256\",\n \"codeVerifier\": \"<base64url>\",\n \"state\": \"<state>\"\n}\n`\n\nThe web application redirects the user's browser to url and stores codeVerifier and state (for example in a short-lived signed cookie) to use in the callback.\n\nOptions:\n\n- --provider — Provider name / config key (required).\n- --clientId — OAuth client ID (also OAUTH_CLIENT_ID) (required).\n- --redirectUri — Redirect URI registered with the provider (required).\n- --scopes — Comma-separated scopes (flag > user config > bundled).\n- --state — Opaque state value (generated as base64url random if omitted).\n- --authUrl — Authorization endpoint URL (flag > user config > bundled).\n- --configFile — Path to a user config.yaml with per-provider settings.\n\n### oauth exchange\n\nExchange the authorization code (returned to your redirect URI) for tokens and build a principal from the provider's userinfo endpoint. The provider's field map renames userinfo fields to canonical claim names (for example GitHub's numeric id → sub); unmapped fields pass through unchanged, so a standard OIDC provider needs no map.\n\n`bash\naux4 oauth exchange --provider github \\\n --tokenUrl https://github.com/login/oauth/access_token \\\n --userinfoUrl https://api.github.com/user \\\n --clientId YOUR_CLIENT_ID --clientSecret YOUR_CLIENT_SECRET \\\n --code 4f9a2c... --codeVerifier IX1jAHuH... \\\n --redirectUri https://app.example/auth/callback \\\n --map '{\"id\":\"sub\",\"login\":\"username\"}'\n`\n\nOutput:\n\n`json\n{\n \"sub\": 4242,\n \"username\": \"octocat\",\n \"name\": \"The Octocat\",\n \"email\": \"octo@example.com\",\n \"provider\": \"github\"\n}\n`\n\n**Note:** The profile is read from the userinfo endpoint. The id_token is not signature-verified against the provider's JWKS yet — JWKS / id_token verification is a planned hardening follow-up.\n\nOptions:\n\n- --provider — Provider name, added to the principal (required).\n- --clientId — OAuth client ID (also OAUTH_CLIENT_ID) (required).\n- --clientSecret — OAuth client secret (also OAUTH_CLIENT_SECRET).\n- --code — Authorization code returned to the redirect URI (required).\n- --codeVerifier — PKCE code verifier produced by authorize-url (required).\n- --redirectUri — Redirect URI used in the authorize-url step; must match (required).\n- --tokenUrl — Token endpoint URL (flag > user config > bundled).\n- --userinfoUrl — Userinfo endpoint URL (flag > user config > bundled).\n- --map — JSON object mapping userinfo fields to principal claims.\n- --configFile — Path to a user config.yaml with per-provider settings.\n\n### Web login flow\n\nauthorize-url and exchange together implement the two halves of a server-side OAuth2 web login:\n\n1. On sign-in, the app calls authorize-url, redirects the browser to the returned url, and stashes codeVerifier and state.\n2. On the callback, the app verifies the returned state, then calls exchange with the code and the stashed codeVerifier to get the principal.\n\nPer-provider URLs and the field map can be stored once in a user config.yaml so the web app only passes credentials and the per-request values:\n\n`yaml\nconfig:\n github:\n authUrl: https://github.com/login/oauth/authorize\n tokenUrl: https://github.com/login/oauth/access_token\n userinfoUrl: https://api.github.com/user\n scopes: read:user,user:email\n map:\n id: sub\n login: username\n`\n\n## Environment Variables\n\n- OAUTH_CLIENT_ID — default for --clientId.\n- OAUTH_CLIENT_SECRET — default for --clientSecret`.\n"}