User Story
As an operator running the OpenShell CLI on a headless machine (no display, no local browser) against an OIDC-secured gateway, I want to authenticate interactively as myself using the standard device authorization grant, so that I don't need a confidential client/service-account credential just to log in as a human user.
Problem Statement
The CLI's interactive OIDC login (openshell gateway add/openshell gateway login with --oidc-issuer) only implements the Authorization Code + PKCE grant. That flow binds an ephemeral 127.0.0.1:<random-port> callback listener on the machine running the CLI and either launches a local browser or prints a URL for the user to open manually — but the OAuth redirect always targets that random loopback port on the CLI host. On a headless host with no browser and no inbound access, there is no way to complete this redirect without manually tunneling a randomly chosen port back to the headless machine before a 120-second timeout expires.
The only fully headless path today is the Client Credentials grant, which requires a confidential client and OPENSHELL_OIDC_CLIENT_SECRET. That's appropriate for service/automation identities, but it does not let a human authenticate as themselves (their own subject, roles, workspace membership) from a headless host.
Impact / Why This Matters
Operators who administer sandboxes from bastion hosts, CI runners used interactively, remote dev boxes, or other displayless environments currently cannot log in to an OIDC gateway as themselves without either:
- Provisioning and distributing a confidential client secret they shouldn't need (weakens the security model — turns a personal login into a shared service credential), or
- Manually engineering an SSH port-forward around a randomly chosen ephemeral port within a two-minute window, which is impractical and not documented anywhere.
This blocks a legitimate, common workflow and pushes users toward using automation credentials for interactive human access, which is a worse security posture (harder to attribute actions to a specific user, harder to revoke individually, no natural token expiry tied to a session).
Proposed Design
Add support for the OAuth 2.0 Device Authorization Grant (RFC 8628) as an additional interactive login mode for OIDC gateways, alongside the existing Authorization Code + PKCE flow.
User-facing workflow:
-
User runs openshell gateway login <name> (or gateway add ... --oidc-issuer ...) on a headless host.
-
The CLI detects it cannot or should not launch a local browser (e.g., no display, OPENSHELL_NO_BROWSER=1, or an explicit new flag such as --device-code) and falls back to the device authorization grant instead of failing outright.
-
The CLI prints a short verification URL and a human-readable code, e.g.:
To authenticate, open this URL on any device with a browser:
https://idp.example.com/device
and enter code: WDJB-MJHT
Waiting for approval...
-
The user completes login and code entry on a separate device (phone, laptop) with no need for any network path back to the headless host.
-
The CLI polls the token endpoint in the background and, once approved, stores the resulting token bundle exactly as the existing Authorization Code flow does today (~/.config/openshell/gateways/<name>/oidc_token.json), including refresh-token based renewal.
-
On denial or expiry, the CLI reports a clear error and exits non-zero.
This should require no new gateway-side (openshell-server) changes — it only affects how the CLI acquires tokens from the OIDC provider. It does require the target IdP's OIDC client to have the device grant enabled, which is IdP-side configuration outside OpenShell's control; the CLI should surface a clear error if the discovery document has no device_authorization_endpoint or the IdP rejects the grant.
Acceptance Criteria
Alternatives Considered
- SSH tunnel the existing PKCE callback port: Works in principle but requires a fixed, predictable callback port (not currently supported — the CLI always binds
127.0.0.1:0) and manual tunnel setup by the user every time. Fragile and undocumented; doesn't scale as a supported workflow.
- Require a confidential client + Client Credentials grant for headless human login: Already works today but authenticates as a shared service identity rather than the individual user, which is a weaker security and auditability posture for interactive human access.
- Do nothing: Leaves headless interactive OIDC login unsupported, forcing users onto one of the two options above.
Agent Investigation
Reviewed crates/openshell-cli/src/oidc_auth.rs: only oidc_browser_auth_flow (Authorization Code + PKCE, public client, ephemeral 127.0.0.1:0 callback listener, 120s timeout) and oidc_client_credentials_flow (confidential client via OPENSHELL_OIDC_CLIENT_SECRET) are implemented. No device_code/RFC 8628 support exists anywhere in openshell-cli, openshell-sdk, or openshell-bootstrap. docs/reference/gateway-auth.mdx confirms the documented behavior matches the code.
User Story
As an operator running the OpenShell CLI on a headless machine (no display, no local browser) against an OIDC-secured gateway, I want to authenticate interactively as myself using the standard device authorization grant, so that I don't need a confidential client/service-account credential just to log in as a human user.
Problem Statement
The CLI's interactive OIDC login (
openshell gateway add/openshell gateway loginwith--oidc-issuer) only implements the Authorization Code + PKCE grant. That flow binds an ephemeral127.0.0.1:<random-port>callback listener on the machine running the CLI and either launches a local browser or prints a URL for the user to open manually — but the OAuth redirect always targets that random loopback port on the CLI host. On a headless host with no browser and no inbound access, there is no way to complete this redirect without manually tunneling a randomly chosen port back to the headless machine before a 120-second timeout expires.The only fully headless path today is the Client Credentials grant, which requires a confidential client and
OPENSHELL_OIDC_CLIENT_SECRET. That's appropriate for service/automation identities, but it does not let a human authenticate as themselves (their own subject, roles, workspace membership) from a headless host.Impact / Why This Matters
Operators who administer sandboxes from bastion hosts, CI runners used interactively, remote dev boxes, or other displayless environments currently cannot log in to an OIDC gateway as themselves without either:
This blocks a legitimate, common workflow and pushes users toward using automation credentials for interactive human access, which is a worse security posture (harder to attribute actions to a specific user, harder to revoke individually, no natural token expiry tied to a session).
Proposed Design
Add support for the OAuth 2.0 Device Authorization Grant (RFC 8628) as an additional interactive login mode for OIDC gateways, alongside the existing Authorization Code + PKCE flow.
User-facing workflow:
User runs
openshell gateway login <name>(orgateway add ... --oidc-issuer ...) on a headless host.The CLI detects it cannot or should not launch a local browser (e.g., no display,
OPENSHELL_NO_BROWSER=1, or an explicit new flag such as--device-code) and falls back to the device authorization grant instead of failing outright.The CLI prints a short verification URL and a human-readable code, e.g.:
The user completes login and code entry on a separate device (phone, laptop) with no need for any network path back to the headless host.
The CLI polls the token endpoint in the background and, once approved, stores the resulting token bundle exactly as the existing Authorization Code flow does today (
~/.config/openshell/gateways/<name>/oidc_token.json), including refresh-token based renewal.On denial or expiry, the CLI reports a clear error and exits non-zero.
This should require no new gateway-side (
openshell-server) changes — it only affects how the CLI acquires tokens from the OIDC provider. It does require the target IdP's OIDC client to have the device grant enabled, which is IdP-side configuration outside OpenShell's control; the CLI should surface a clear error if the discovery document has nodevice_authorization_endpointor the IdP rejects the grant.Acceptance Criteria
openshell gateway login/gateway addcan complete OIDC authentication against a public client without any local browser and without a client secret, using the device authorization grant.intervaland handlesauthorization_pending,slow_down,access_denied, andexpired_tokenper RFC 8628.device_authorization_endpoint.docs/reference/gateway-auth.mdxdescribes the new mode and when it applies.Alternatives Considered
127.0.0.1:0) and manual tunnel setup by the user every time. Fragile and undocumented; doesn't scale as a supported workflow.Agent Investigation
Reviewed
crates/openshell-cli/src/oidc_auth.rs: onlyoidc_browser_auth_flow(Authorization Code + PKCE, public client, ephemeral127.0.0.1:0callback listener, 120s timeout) andoidc_client_credentials_flow(confidential client viaOPENSHELL_OIDC_CLIENT_SECRET) are implemented. Nodevice_code/RFC 8628 support exists anywhere inopenshell-cli,openshell-sdk, oropenshell-bootstrap.docs/reference/gateway-auth.mdxconfirms the documented behavior matches the code.