Appearance
plexctl login
Synopsis
plexctl login authenticates an operator against a plexsphere control plane using the RFC 8628 OAuth 2.0 Device Authorisation Grant. The CLI posts a device-code request to the resolved server, prints a verification URL plus a short user code, and polls the device-token endpoint until the operator completes approval in a browser. On success the issued bearer token is persisted to the plexctl config file at mode 0600 under the named profile (defaults to default), so subsequent CLI invocations resolve credentials without flags .
The command is the only path that writes to the plexctl config file. The persisted shape — an ~/.config/plexctl/config.json document carrying a map of named profiles plus a default pointer — is the canonical credential surface for every other subcommand .
Invocation
text
plexctl login --domain-id <uuid> --idp-binding-id <uuid> [--profile-name <name>] [persistent flags]The command takes no positional arguments. Both --domain-id and --idp-binding-id are required; the values address a specific IdP binding inside a Domain so the device-code surface knows which upstream identity provider to drive.
Flags
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--domain-id | UUID | yes | — | Domain UUID for the device-code session. The Domain owns the IdP binding addressed by --idp-binding-id; mismatched ids surface as 400 Bad Request. |
--idp-binding-id | UUID | yes | — | IdP binding UUID within the Domain. Identifies the upstream OIDC provider whose authorisation endpoint the verification URL points at. |
--profile-name | string | no | default | Name to save the resolved profile under in the plexctl config file. The first profile created also becomes the file-level default pointer (subsequent invocations without --profile resolve it transparently). |
Persistent flags inherited from root
plexctl login reads --server from the persistent flag bag to resolve the API URL it posts the device-code request against. The --token-file flag is inert here (login is the surface that produces the token). See plexctl.md for the canonical list of persistent flags (--server, --profile, --token-file, --output, --yes, --reveal-secrets).
Exit codes
plexctl collapses every failure into the taxonomy below; the single source of truth is exitCodeFor in ../../../cmd/plexctl/app.go.
| Code | Reachable | Meaning |
|---|---|---|
0 | yes | The device-code session completed and the token was persisted. |
1 | yes | Runtime / API error: transport failure, malformed device-code response, RFC 8628 expired_token / access_denied / invalid_grant, or session-deadline timeout. |
2 | yes | Flag-parse / misconfiguration: missing --domain-id / --idp-binding-id, malformed UUID, unknown --output format. |
3 | yes | No server resolved (--server not set, PLEXSPHERE_URL empty, no profile) — login still needs to know where to post the device-code request. |
4 | rare | Permission denied — surfaces only when the configured IdP rejects the operator at approval time and the upstream maps the denial to HTTP 403. |
64 | no | Not reachable — login is fully implemented and never returns *NotImplementedError. |
Examples
Happy path against the kind dev stack
shell
export PLEXSPHERE_URL="${PLEXSPHERE_URL:-https://localhost:8080}"
plexctl login \
--server "${PLEXSPHERE_URL}" \
--domain-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
--idp-binding-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0b0The CLI prints the verification URL and user code, then blocks on the poll loop. After approval in the browser, stdout reports Logged in. Saved profile "default". and a subsequent plexctl whoami succeeds without further flags.
Expired session (exit 1)
shell
plexctl login \
--server "${PLEXSPHERE_URL}" \
--domain-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
--idp-binding-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0b0
# operator does not approve before the session deadline
# stderr: plexctl: device-code session expired before approval; restart with `plexctl login`
echo $? # 1The polling loop short-circuits once the local clock passes the device-code expires_in; the typed auth.ErrSessionExpired error maps to exit 1.
JSON summary for CI scripts
shell
plexctl login \
--server "${PLEXSPHERE_URL}" \
--domain-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
--idp-binding-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0b0 \
--output jsonIn json / yaml mode the verification prompt still goes to stdout (it is the primary success-path output), but a structured summary follows on completion:
json
{
"profile": "default",
"server": "https://localhost:8080",
"token_type": "Bearer",
"expires_in": 3600,
"has_token": true
}The bearer string itself is intentionally omitted — no secret ever crosses an unguarded surface; the persisted config file is the canonical store.
Cross-references
../../../api/openapi/plexsphere-v1.yaml—PostAuthDeviceCode(/v1/auth/device-code) andPostAuthDeviceToken(/v1/auth/device-token) operation definitions and theDeviceCodeResponse/ServiceTokenResponseschemas.../../contexts/identity/idp.md— bounded-context reference for the IdP binding aggregate the--domain-id/--idp-binding-idpair addresses.../../../cmd/plexctl/commands/login.go— source of truth for the cobra command, flag wiring, and the device-code request shape.../../../cmd/plexctl/internal/auth/devicecode.go— RFC 8628 polling loop (authorization_pending,slow_down, terminal-error mapping, session-expiry semantics).