Repository navigation
feat(providers): add gateway-owned interactive OAuth login framework #3331
Description
User Story
As an OpenShell user, I want to authorize a subscription-backed upstream provider through the OpenShell CLI, so that the gateway can own and refresh a dedicated OAuth grant without importing credentials from another host application or exposing reusable tokens to a sandbox.
Problem Statement
OpenShell has gateway-managed refresh for provider credentials, credential-driver-backed secret storage, provider attachment, native endpoint policy, and egress credential injection. It does not have a common way to bootstrap an interactive OAuth grant for a provider.
Current and proposed subscription-provider integrations therefore implement or propose provider-specific setup outside the shared provider lifecycle:
- feat: oauth support for Claude Pro/Max subscription plans #1925 / feat(providers): anthropic subscription oauth #2285 imports an existing Claude Code login from host Keychain or files.
- feat(providers): add gateway-owned OpenAI Codex subscription OAuth provider #2740 proposes a dedicated OpenAI Codex subscription grant.
- feat(providers): add gateway-owned xAI Grok subscription OAuth provider #2742 proposes a dedicated xAI Grok subscription grant.
scripts/agents/gatorcurrently reads Codex access, refresh, and account values from host~/.codex/auth.json, creates a provider, configures gateway refresh material, and repairs refresh state from the host login when rotation fails.
Importing an existing application's OAuth state gives two clients ownership of one rotating refresh-token lineage. It also couples provider setup to host-specific files, credential formats, and installed applications. Implementing separate CLI flags and lifecycle code for every provider would duplicate browser/device interaction, persistence, cancellation, status, refresh installation, and failure handling.
OpenShell needs one gateway-owned interactive login lifecycle with small, trusted provider-specific adapters.
Impact / Why This Matters
Without this capability, users must choose between signing in inside a sandbox, copying reusable credentials, harvesting another application's host login, or operating a separate host-side inference gateway. These workarounds weaken the sandbox credential boundary, create refresh races, depend on unstable private file formats, and make remote gateways difficult to operate.
A common login framework would make subscription providers behave like normal OpenShell providers: authorize once through the CLI, attach the provider to a sandbox, and let the gateway persist and refresh the grant through its configured credential driver. It would also prevent Codex, Grok, Claude, and future providers from each creating incompatible setup commands and token-storage paths.
Proposed Design
User experience
A login-capable provider can be created without importing or supplying a reusable OAuth credential:
openshell provider create --name codex --type codex-subscription
openshell provider login codexprovider login starts a new authorization grant dedicated to that OpenShell provider instance. It must not read or reuse Codex, Claude Code, Grok, or other host application credential stores.
The provider, login session, resulting grant, refresh state, and credential handles belong to exactly one workspace. The CLI uses its selected workspace, and every login, status, completion, cancellation, reauthorization, logout, attachment, and credential-resolution operation must enforce that workspace scope. Providers with the same name in different workspaces remain independent.
For a device-authorization provider, the CLI prints the verification URL and user code, optionally opens the URL, reports expiry, waits for completion, and supports cancellation. For an authorization-code provider, the CLI opens the authorization URL and completes a PKCE-protected browser flow. The interaction must work when the CLI and gateway are on different machines.
The same command reauthorizes a provider in reauthorization_required state. Users can inspect bounded, non-secret status and log out:
openshell provider login status codex
openshell provider logout codexExact command nesting may follow existing CLI conventions, but login, status, cancellation, reauthorization, and logout must be provider-neutral rather than separate --login-codex, --login-xai, or --login-claude flows.
After login, attaching the provider authorizes its declared native inference endpoints and egress credential injection. The workload calls the provider's native endpoint; this design must not introduce or depend on inference.local.
End-to-end interaction
sequenceDiagram
autonumber
actor U as User
participant C as OpenShell CLI
participant G as Gateway
participant I as Provider OAuth service
participant D as Credential driver
participant M as Gateway metadata store
participant S as Sandbox
participant E as Egress enforcement
participant A as Provider inference API
U->>C: Run provider login for a named provider
C->>G: Start login in the selected workspace
G->>G: Bind an expiring session to workspace provider and version
alt Device authorization
G->>I: Request device authorization
I-->>G: Return verification URL user code and polling interval
G-->>C: Return device interaction
C-->>U: Display URL and user code
U->>I: Approve the dedicated OpenShell grant
G->>I: Poll for completion
else Browser authorization with PKCE
G-->>C: Return authorization URL and login ID
C-->>U: Open browser
U->>I: Approve the dedicated OpenShell grant
C->>G: Relay callback result for the login ID
G->>I: Exchange code using gateway held PKCE verifier
end
I-->>G: Return access token refresh token expiry and account metadata
G->>D: Stage access token and gateway only refresh material
D-->>G: Return opaque credential handles
G->>M: Atomically commit workspace scoped handles and refresh state
G-->>C: Mark provider ready
C-->>U: Report login succeeded
opt Refresh before credential expiry
G->>D: Resolve refresh material by handle
D-->>G: Return refresh material
G->>I: Refresh the grant
I-->>G: Return rotated tokens and expiry
G->>D: Stage replacement credential values
D-->>G: Return replacement handles
G->>M: Version matched commit of handles and expiry
G->>D: Delete superseded handles after commit
end
S->>E: Request a native provider endpoint
E->>G: Resolve attached provider for sandbox and workspace
G->>M: Verify workspace scoped provider attachment and state
G->>D: Resolve current access credential
D-->>G: Return current credential
G-->>E: Return authorized policy and credential
E->>A: Inject credential and forward request
A-->>S: Return inference response
Shared behavior
The common framework owns:
- authorization-session creation, expiry, polling or callback completion, cancellation, and cleanup;
- PKCE and OAuth state generation and validation;
- generic CLI rendering for device-code and browser interactions;
- binding a login session to the authenticated caller, exact workspace, provider ID, provider type, and provider resource version, with no cross-workspace lookup or fallback;
- atomic installation of the initial access token, refresh material, expiry, account metadata, and refresh state;
- storage through the gateway's configured credential driver, including the default database credstore and configured Vault, Kubernetes Secrets, or external drivers;
- refresh, rotated refresh-token persistence, retry classification, and
reauthorization_requiredtransitions using the existing gateway refresh machinery; - re-login, logout, remote revocation where supported, local deletion, and cleanup of superseded credential handles;
- redacted status and audit events that never include codes, tokens, callback parameters, or credential-bearing URLs;
- race handling for concurrent login, refresh, provider update, provider deletion, and logout operations;
- fail-closed behavior when credentials expire, the credential driver is unavailable, or refresh requires user action.
Provider metadata stores only non-secret state and opaque credential handles. Access tokens, refresh tokens, and other reusable authorization material are stored through the configured credential driver. Neither login nor refresh returns reusable credentials to the CLI or sandbox.
Refresh scheduling and credential-driver storage remain workspace-scoped through the provider identity. A login or refresh in one workspace must not read, replace, revoke, publish, or delete the grant or credential handles of a provider in another workspace.
Provider-specific behavior
Each supported provider supplies a trusted adapter for its documented or explicitly supported authorization contract. An adapter may define:
- supported interaction type: device authorization, authorization code with PKCE, or both;
- authorization, device-code, token, account, and revocation endpoints;
- OAuth client identity, scopes, audience, and required provider parameters;
- token and error response parsing, including retryable versus terminal failures;
- account, organization, workspace, or subscription binding that must be preserved with the grant;
- provider-specific logout/revocation behavior;
- required inference origins, paths, headers, and request transformations.
The CLI should not implement provider token exchange or persistence. It may render bounded provider-supplied guidance, such as an account setting required before device authorization. The gateway remains the sole owner and refresher of the grant.
Interactive login configuration is security-sensitive because it controls where reusable credentials are sent. Initial support should use trusted built-in adapters. Custom profiles must not be able to redirect a built-in adapter's authorization, token, refresh, revocation, or credential-bearing inference endpoints to arbitrary origins.
Initial provider scope
The first implementation should target OpenAI Codex and Claude Code together so the shared framework is proven across both interaction families:
- Codex is the first device-authorization adapter. It creates a new ChatGPT/Codex grant owned exclusively by the OpenShell provider and does not read or reuse host Codex authentication.
- Claude Code is the first browser-authorization adapter. It creates a new Claude subscription grant owned exclusively by the OpenShell provider and does not read macOS Keychain entries,
~/.claude/.credentials.json, or any other existing Claude Code login. This depends on Anthropic providing or approving a client contract suitable for an independent OpenShell-owned grant.
The two adapters should share login-session, persistence, status, logout, and refresh plumbing while retaining their provider-specific authorization protocol, account binding, native inference endpoints, headers, and request transformations. Grok remains a follow-on device-authorization adapter under #2742. This issue creates the shared capability; #1925, #2740, #2742, and future provider issues remain responsible for provider-specific compatibility and rollout decisions.
Persistence and refresh lifecycle
A successful login should feed the same provider credential and refresh model already used by gateway-managed refresh rather than establishing a parallel token store.
The initial token installation and subsequent rotations must be version-matched. If storage or metadata commit fails, staged credential handles are removed and the provider's previously committed grant remains authoritative. A late login completion must not resurrect a deleted provider or overwrite a newer login, logout, reconfiguration, or refresh result.
After installation, the existing gateway refresh worker resolves gateway-only refresh material from the configured credential driver, refreshes before expiry, persists a rotated refresh token when returned, publishes the replacement access credential to provider resolution, and marks terminal grant failures as requiring reauthorization. Provider-specific adapters may extend request construction and bounded error classification without duplicating scheduling, storage, concurrency, or status machinery.
Acceptance Criteria
-
openshell provider login <name>starts a provider-neutral interactive authorization flow for a login-capable provider without reading credentials from another host application. - The common API represents at least device-code and browser-PKCE interactions, including expiry, completion, cancellation, and sanitized errors.
- The flow works when the CLI and gateway run on different machines.
- A successful login creates a distinct grant owned and refreshed exclusively by the gateway.
- The login session, grant, credential handles, refresh state, status, logout, and provider attachment are bound to one explicit workspace; same-named providers in other workspaces remain isolated.
- Initial access credentials and gateway-only refresh material are stored through the configured credential driver; provider metadata contains opaque handles and non-secret lifecycle state.
- Login atomically installs credential handles, expiry, account binding, and refresh configuration, with cleanup after partial storage or a lost version race.
- The existing gateway refresh lifecycle refreshes the installed grant, persists rotated refresh tokens, publishes updated access credentials, and reports
reauthorization_requiredfor terminal failures. - Re-login safely replaces a grant without allowing an older login or refresh operation to overwrite the replacement.
- Logout cancels pending login, prevents concurrent refresh publication, revokes remotely when supported, deletes local credential material, and causes subsequent credentialed requests to fail closed.
- A sandbox with the provider attached can call only its profile-authorized native endpoints and receives egress credential injection without receiving the reusable token.
- A sandbox without the provider attached cannot use the grant, and a custom endpoint cannot receive credentials from a built-in login adapter.
- Login, refresh, logout, diagnostics, logs, and audit events do not expose access tokens, refresh tokens, user/device codes, authorization codes, PKCE verifiers, or credential-bearing URLs.
- The initial implementation includes a Codex device-authorization adapter and a Claude Code browser-authorization adapter, each creating a distinct gateway-owned grant without importing host application credentials.
- Codex and Claude Code have provider-specific protocol tests plus gateway/credential-driver integration coverage demonstrating that both adapters use the same shared login and persistence lifecycle.
- Shared contract tests cover device approval, denial, expiration, cancellation, polling backoff, transient token errors, terminal grant rejection, rotated refresh tokens, driver failure, provider deletion, reconfiguration, and concurrent re-login/refresh/logout.
- Gator can use a persistent gateway-owned Codex provider without reading
~/.codex/auth.json, exporting host Codex tokens, or resetting refresh state from host credentials. - Published provider documentation explains login, attachment, status, reauthorization, logout, native endpoint behavior, remote-gateway interaction, and credential-driver persistence.
Alternatives Considered
Import an existing host application login
Rejected as the normal workflow. It gives OpenShell and the host application competing ownership of a rotating refresh-token lineage, depends on private host storage formats, and makes remote gateways awkward. A dedicated OpenShell grant has one owner and one refresh lifecycle.
Authenticate inside the sandbox
Rejected. It places reusable subscription credentials inside the less-trusted workload and bypasses the provider credential boundary.
Implement independent CLI login flags for every provider
Rejected. It duplicates interaction, storage, refresh installation, cancellation, status, and failure handling. Provider-specific protocol differences belong behind a common provider-login capability.
Continue using a separate host inference gateway
This can remain an optional integration, but it adds another daemon, credential boundary, routing layer, and operational lifecycle. It should not be required to use a subscription-backed provider safely.
Expose authorization entirely through provider profile YAML
Rejected for built-in subscription adapters. Arbitrary authorization and token endpoints would let profile data redirect reusable credentials. Profiles can describe provider capabilities and native egress policy, while trusted code owns credential-bearing protocol contracts.
Agent Investigation
- feat(providers): add gateway-owned credential refresh for short-lived provider tokens #1306 implemented the gateway-owned provider refresh foundation.
- feat: oauth support for Claude Pro/Max subscription plans #1925 / feat(providers): anthropic subscription oauth #2285 demonstrates the desired Claude subscription outcome but currently harvests an existing host Claude Code login.
- feat(providers): add gateway-owned OpenAI Codex subscription OAuth provider #2740 and feat(providers): add gateway-owned xAI Grok subscription OAuth provider #2742 describe distinct gateway-owned Codex and Grok grants and identify common lifecycle and isolation requirements.
scripts/agents/gator/agent.yamlcurrently reads Codex access, refresh, and account values from~/.codex/auth.json.scripts/agents/run.shcreates or updates the provider, configures refresh material, rotates it, and can reset failed gateway refresh state from the host auth file.openshell-server::credentialsalready stores provider credentials and gateway-only refresh material through the active credential driver.openshell-server::provider_refreshalready schedules refresh, resolves refresh material, rotates access and refresh tokens, persists expiry, and reports recovery state.- Provider profiles already contribute native endpoint policy and egress credential injection;
inference_capableis informational and does not require aninference.localrouting abstraction.
Activity
johntmyers commentedon Sep 14, 2026
Implementation Plan
Issue type:
featComplexity: High
Confidence: Medium — the shared framework is grounded in current code. Each production adapter requires evidence of a supported independent OpenShell client contract; Claude Code remains dependent on Anthropic approval.
Summary
Add a provider-neutral, gateway-owned interactive OAuth lifecycle covering device authorization and browser PKCE. The CLI renders user interaction and relays callback results, while the gateway owns provider HTTP exchanges, session state, PKCE/device secrets, credential-driver persistence, refresh, reauthorization, and logout.
Preserve existing API-key and host-discovery profiles. Add dedicated trusted subscription profiles and bind each login adapter to an exact built-in profile ID. Sandbox traffic continues through native provider endpoints and existing endpoint-bound credential injection; do not introduce
inference.localor a sandbox credential store.Scope
proto/openshell.proto, generated Python bindings underpython/openshell/_proto/, committed Go bindings undersdk/go/proto/openshellv1/, plus Rust and SDK mocks affected by the expandedOpenShellservice. TypeScript files undersdk/typescript/src/gen/remain generated build output.crates/openshell-server/src/provider_login/andcrates/openshell-server/src/grpc/provider_login.rs; wire them throughcrates/openshell-server/src/grpc/mod.rsandcrates/openshell-server/src/lib.rs.crates/openshell-server/proto/provider_login.protoin a new storage package and updatecrates/openshell-server/build.rs. Do not modify the frozenopenshell.storage.v1schema in place.crates/openshell-server/src/persistence/mod.rs,sqlite.rs,postgres.rs, and persistence tests.crates/openshell-server/src/credentials.rs,provider_refresh.rs, and relevant lifecycle code ingrpc/provider.rs.crates/openshell-core/src/provider_credentials.rs, the settings loop incrates/openshell-sandbox/src/lib.rs, and HTTP/WebSocket injection paths as required for authorization epochs, freshness deadlines, and logout acknowledgments.crates/openshell-server/src/provider_login/. Select adapters using exact trusted built-in profile IDs, never caller-controlled endpoint data.providers/codex-subscription.yamlandproviders/claude-code-subscription.yaml, registered and validated throughcrates/openshell-providers/src/profiles.rs. Keepproviders/codex.yamlandproviders/claude-code.yamlbackward compatible.crates/openshell-cli/src/main.rsandcrates/openshell-cli/src/commands/provider.rs; add provider-neutral callback/rendering support. Reuse only the loopback/browser primitives fromoidc_auth.rs; provider token exchange and persistence remain in the gateway.crates/openshell-cli/tests/provider_commands_integration.rsand service mocks such ascrates/openshell-sdk/tests/client_mock.rs.scripts/agents/run.sh,scripts/agents/gator/agent.yaml,scripts/agents/gator/providers/codex-gator.yaml,scripts/agents/runtime/harnesses/codex/exec.sh, and related READMEs.architecture/gateway.md,docs/sandboxes/manage-providers.mdx,docs/providers/profiles.mdx, anddocs/get-started/quickstart.mdx; add provider-specific pages anddocs/index.ymlentries only if needed.Implementation Steps
Define the provider-neutral RPC contract and CLI shape.
WorkspaceSelectoron every RPC. Bind each opaque login ID to the exact provider ID, stable authenticated principal identity, workspace, trusted adapter, configuration generation, and replacement-session generation. Keep ordinary active-grant refresh versions separate so refresh cannot invalidate a pending replacement login.provider login <name>andprovider logout <name>, with status and cancellation following existing Clap conventions.Add a durable, replica-safe login-session state machine.
CredentialRuntime, scoped by workspace/provider/session object ID.slow_down, expiration, and gateway rate limits.Introduce a small trusted adapter interface.
Add atomic local grant publication with upstream-outcome recovery.
provider_refresh::refresh_provider_credentialto remove the split refresh-state/provider publication race.Implement provider-neutral CLI interaction.
Implement the Codex device-authorization adapter and subscription profile.
codex login --device-authalone does not establish that independent client contract. Confirm a new login creates a separate grant lineage.~/.codex/auth.json. If the contract is unavailable, leave production Codex support gated while exercising the framework with test adapters.Gate Claude Code on an approved vendor contract.
~/.claude/.credentials.json, embed a confidential secret, or guess undocumented endpoints.Migrate Gator to an already-authorized gateway provider.
gateway_existingthat attaches a named provider but neither imports host credentials nor creates/reconfigures its refresh grant.file_jsonsources, embedded client identity, and host-sourced refresh configuration.scripts/agents/run.shfor this provider and reportprovider loginas remediation when the grant is absent or requires reauthorization.Add observability, documentation, and rollout safeguards.
from_existingflows. Roll out storage/recovery and enforcement capabilities first, shared RPC/session support next, Codex after its contract gate, Gator after authorization, and Claude after its contract gate. Do not enable interactive grants until all serving replicas and consuming supervisors support the required state/enforcement semantics. Document downgrade behavior and reject unsupported mutations instead of silently using legacy refresh.sync-agent-infra, including Gator and provider operation guidance. Update architecture, published docs, and gateway configuration references for any new freshness or cleanup settings.Test Plan
Unit tests:
slow_down, backoff, transient errors, and malformed or oversized responses.invalid_grantmapping without provider prose or token leakage.Integration tests:
E2E tests:
~/.codex/auth.json, host token exports,GATOR_CODEX_OAUTH_CLIENT_ID, or reset-from-host logic is consulted.mise run pre-commit,mise run test,mise run cibefore opening a PR, focused provider-login/refresh tests,mise run e2e:provider-refresh-keycloak, and relevant Docker/Podman and Kubernetes credential-driver lanes.Required Failure and UX Coverage
Delivery and Acceptance
The shared framework and each approved adapter may land separately. The issue remains incomplete until both production adapter acceptance items are satisfied; mock adapters prove the framework but do not satisfy production Codex/Claude acceptance. Record blocked vendor-contract items explicitly. Gator migration depends on a working authorized Codex provider. Keep the issue's existing acceptance criteria and add the recovery, concurrency, permission, account-binding, and bounded logout contracts above to implementation verification.
Risks & Open Questions
Documentation Impact
Update
architecture/gateway.md,docs/sandboxes/manage-providers.mdx,docs/providers/profiles.mdx,docs/get-started/quickstart.mdx, and Gator documentation. Explain that each grant belongs to one workspace/provider, reusable tokens remain in the configured credential driver, the CLI can target a remote gateway, refresh is gateway-owned, workloads call native endpoints, and reauthorization/logout use the generic lifecycle.Document authorization freshness, logout enforcement, recovery, cleanup retention, and account-change behavior. Decide whether freshness/cleanup limits are fixed protocol bounds or configurable gateway settings; do not assume there are no configuration changes. Update
docs/reference/gateway-config.mdxfor any new fields/defaults and to clarify that existingcredential_driversalso store session secrets, grant material, and recovery material. If Anthropic requires per-deployment registration or a confidential client secret, treat that as a separate design decision requiring secure gateway configuration and a full gateway-config documentation update.Revision 2 — incorporates security, failure-recovery, supervisor enforcement, authorization, account-binding, UX, and staged-delivery review.
github-actions commentedon Oct 16, 2026
This issue has had no activity for 14 days and is now marked stale. It may be closed in 14 days if there is no further activity. Comment or remove the state:stale label to keep it open.
klcd commentedon Oct 21, 2026
Will this be implemented or is there a workaround that is being used?
houman44 commentedon Oct 21, 2026
Data point while this is open: a custom provider profile, a copy of
claude-codewithenv_vars: [CLAUDE_CODE_OAUTH_TOKEN],auth_style: bearer,header_name: authorizationand endpoints reduced toapi.anthropic.com, works with aclaude setup-tokentoken on v0.1.5.claude -pran inside the sandbox, and the sandbox only saw the placeholder. It is undocumented;providers/claude-code.yamland the docs cover API keys only and tell subscription users to create a Console key.