Skip to main content

crewship oauth

Drive the OAuth connect flow for an OAUTH2 credential — the flow that turns an empty credential row into one that actually holds tokens — without a dashboard.

Subcommands

Every subcommand honours the global --format flag (json/yaml/ndjson) — see Output Formats.

The two legs

The flow has two shapes and they are alternatives, not steps. Pick the one your network allows.
connect asks the server to bind a temporary listener on its own 127.0.0.1, hands back an authorize URL pointed at that listener, and completes the token exchange itself when the browser lands on the redirect.
The browser must be able to reach 127.0.0.1:<port> on the API host. On a laptop or a dev box that is the same machine; against a remote server it is not.
Keep the state token. The server stored the PKCE code_verifier against it and can only recover the verifier when the state comes back with the code. An exchange with neither --state nor --code-verifier goes out with no verifier, which a provider that enforces PKCE (Google, Linear) rejects with invalid_grant. The command warns when you are about to do that.

Creating the credential first

Both legs operate on a credential that already carries the OAuth app details. Create it with crewship credential create --type OAUTH2:
An app’s client secret outlives every token it issues, so keep it out of argv — an argument is readable by anything that can see the process table for as long as the command runs, and lands in shell history besides. --oauth-client-secret <value> still works for a throwaway or a public client. --oauth-provider fills the authorize URL, token URL and default scopes from the same catalogue crewship oauth providers prints. For a provider the catalogue does not carry — a self-hosted GitLab, a private IdP — give the endpoints yourself:
The row is created with no value and status PENDING. It stays that way, and fails every agent run that resolves it, until the flow below completes. The --oauth-* flags are rejected on any type other than OAUTH2 (exit 2) — the server would silently drop them, which is worse than refusing them. They are also rejected alongside --value/--value-stdin: an OAuth credential’s value is the token the flow fetches, so passing both is asking for the row to be filled two ways and one of them to be discarded. Both refusals key off the flag being named, not off it carrying anything: --oauth-client-secret "", an empty --oauth-client-secret-stdin stream and --value "" are refused exactly like populated ones. They are also decided before anything reads stdin, which is what makes --value-stdin --oauth-client-secret-stdin a refusal rather than a coin toss — there is one stream and two readers, and whichever went first would take the line and leave the other silently empty. Filing a token you obtained elsewhere is still the older, flag-free form, and still works — it just does not involve an app:

crewship oauth providers

This is a static catalogue compiled into the server, not a list of providers you have configured. It tells you which providers you can point a credential at without looking their endpoints up yourself. Under --format json the map is flattened into a sorted array with the slug on a provider key, so it can be iterated rather than only indexed.

crewship oauth connect

By default it waits. There is no OAuth-specific “is it done yet” endpoint, so the only truthful completion signal is the credential flipping to ACTIVE as the tokens are stored — that is what the command polls.
A wait that runs out is a failure, not a tick. It exits non-zero and names the status the credential is stuck in:
Reporting success there would leave the tokens absent and the next agent run would be the thing that found out.
The server’s listener tears itself down after 120 s. The default 3m outlasts that on purpose: a --timeout shorter than the window gives up while the flow is still live, and one merely equal to it loses on any scheduling jitter. A failed status read mid-wait is retried until the deadline rather than abandoning the flow — the loopback listener is one-shot, so giving up on a dropped connection would cost you the consent you already granted. A 401, 403 or 404 is not retried: the credential is gone or unreadable, and waiting does not change that.

crewship oauth authorize

Prints the URL, the state token, and the exact exchange command that finishes the flow.

crewship oauth exchange


crewship oauth discover

Read-only. The server fetches the target’s RFC 9728 protected-resource document and then its RFC 8414 authorization-server document, through an SSRF-guarded client that refuses private and loopback addresses. Supports DCR: true means auto-connect can register a client for you. false means you have to create an OAuth app in the provider’s own settings. Source: known_provider means discovery failed and the URL was matched against the built-in catalogue instead.

crewship oauth auto-connect

The endpoint accepts a provider_hint, and this command deliberately never sends one. Supplying it makes the server fill the authorize URL from the static catalogue and skip discovery entirely, which leaves it with no registration endpoint, which makes Dynamic Client Registration impossible — so every hinted call returns needs_client_id and no credential is ever created. A flag that can only fail is worse than no flag; use crewship oauth providers if what you wanted was the endpoints.
Discovers the endpoints, performs RFC 7591 Dynamic Client Registration, creates an OAUTH2 credential in PENDING, and prints the URL to authorize. Finish with crewship oauth connect <credential-id>.
When the provider offers no registration endpoint the server answers 200 with status: "needs_client_id" and creates nothing. This command treats that as a failure (exit 2) and prints the server’s explanation plus the credential create invocation to run instead — reporting it as success would leave you looking for a credential that does not exist.

Requirements

Errors