Repository navigation
bug(cli): client-credentials gateway login stops working when the access token expires #4287
Description
User Story
I use OpenShell from automation (scripts and CI-style jobs) that authenticate to an OIDC-protected gateway with a confidential service-account client. I log in once with openshell gateway login <name> using OPENSHELL_OIDC_CLIENT_SECRET, then run a sequence of CLI commands. Once the first access token expires, every CLI command fails until I log in again. This breaks any job that runs longer than the provider's access token lifetime.
Problem Statement
When OPENSHELL_OIDC_CLIENT_SECRET is set, openshell gateway login uses the OAuth 2.0 client credentials grant. Client credentials token responses normally carry no refresh token, so the stored token has only an access token and an expiry.
When that access token nears expiry, the CLI tries to renew it with the refresh token grant only. With no refresh token stored, renewal fails with no refresh token available. Once the token has actually expired, the CLI fails closed and asks the user to run openshell gateway login again, even though OPENSHELL_OIDC_CLIENT_SECRET is still in the environment and a new token could be obtained without user interaction.
Code references (unchanged between 8719fc9 and main at 3fc93e2):
crates/openshell-cli/src/oidc_auth.rs:268oidc_client_credentials_flowobtains the token; the resulting bundle hasrefresh_token: None(see the doc comment onOidcTokenBundleincrates/openshell-bootstrap/src/oidc_token.rs:21).crates/openshell-cli/src/oidc_auth.rs:472-481oidc_refresh_tokenreturnsno refresh token availablewhenrefresh_tokenisNone. There is no client credentials fallback.crates/openshell-cli/src/main.rs:181-226command auth setup callsoidc_refresh_tokenwhen the token is within 30 seconds of expiry; after real expiry it returnsOIDC token refresh failed: ... run openshell gateway login <name>.- The same refresh-only path is used by
ensure_valid_oidc_token_bundle(oidc_auth.rs:517-545, called fromcommands/provider.rs:914) and by shell completion (completers.rs:145).
The SDKs already renew client credentials tokens by repeating the grant (#2803, #2907). The CLI does not.
Impact / Why This Matters
Automation that uses the CLI with a service account has to run openshell gateway login <name> before every command, or wrap commands in retry logic that detects this error and logs in again. Each extra login is an additional token request to the identity provider and an extra step that scripts must get right.
In the 30 seconds before expiry, commands print Warning: OIDC token refresh failed; continuing with the cached token, which looks like a transient problem rather than a missing capability. After expiry, long-running jobs fail partway through even though their credentials are valid. The error text also suggests an interactive re-login, which is misleading for a non-interactive client.
Acceptance Criteria
- When the stored token came from a client credentials login and
OPENSHELL_OIDC_CLIENT_SECRETis set, the CLI obtains a new access token with the client credentials grant before or at expiry, stores it, and the command succeeds without a manualgateway login. - Renewal reuses the gateway's registered issuer, client ID, audience, and scopes.
- The client secret is never written to the token cache, metadata, or logs.
- If the token came from client credentials but
OPENSHELL_OIDC_CLIENT_SECRETis no longer set, the error says the secret is required to renew, instead ofno refresh token available. - Browser and device-code logins keep their current refresh token behavior.
- The same renewal applies to every CLI path that renews OIDC tokens (command auth, provider commands, shell completion).
- A test covers an expired client credentials bundle being renewed without a refresh token.
Reproduction Steps
Prerequisites: an OpenShell gateway with OIDC authentication enabled, and any OIDC provider that supports the client credentials grant. Configure a confidential client with service accounts enabled and a short access token lifetime (for example 5 minutes) whose tokens the gateway accepts.
- Register the gateway with OIDC, using the confidential client ID:
openshell gateway add <endpoint> --name cc-test --oidc-issuer <issuer-url> --oidc-client-id <client-id>
- Log in with client credentials:
Output includes
export OPENSHELL_OIDC_CLIENT_SECRET=<client-secret> export OPENSHELL_GATEWAY=cc-test openshell gateway login cc-test
Authenticated via client credentials. - Run
openshell sandbox list. It succeeds. - Wait until the access token lifetime has passed (about 5 minutes in this setup), keeping
OPENSHELL_OIDC_CLIENT_SECRETset. - Run
openshell sandbox listagain.
Expected: the CLI renews the token with client credentials and the command succeeds.
Actual: the command fails with OIDC token refresh failed: no refresh token available and asks for openshell gateway login cc-test.
Suggested UX
No new flags are needed. With the secret in the environment, renewal is silent:
export OPENSHELL_OIDC_CLIENT_SECRET=<client-secret>
openshell gateway login cc-test
# ...more than one token lifetime later...
openshell sandbox list # succeeds, token renewed via client credentialsWithout the secret:
Error: OIDC access token for gateway 'cc-test' expired. It was issued via client credentials;
set OPENSHELL_OIDC_CLIENT_SECRET to renew it automatically, or run `openshell gateway login cc-test`.
Environment
- OpenShell CLI built from commit 8719fc9 (
openshell --versionreports 0.1.3-dev). Relevant code is unchanged on main at 3fc93e2. - Gateway with OIDC authentication; identity provider is Keycloak with a confidential service-account client (any client-credentials-capable provider should reproduce).
- Access token lifetime: 5 minutes.
Logs
Error: OIDC token refresh failed: no refresh token available — re-authenticate with: openshell gateway login
cached OIDC token has expired; run `openshell gateway login cc-test`
Within 30 seconds before expiry:
Warning: OIDC token refresh failed; continuing with the cached token: no refresh token available — re-authenticate with: openshell gateway login
Activity