Skip to content

plexctl platform-idp

plexctl platform-idp is the CLI surface over platform-scoped (shared) IdP bindings. A platform binding is owned by no Domain and is usable by any Domain, so a single upstream IdP — for example one GitHub OIDC app — can authenticate users into more than one Domain instead of forcing a duplicate binding per Domain. It groups two operations against /v1/admin/platform-idp:

  • plexctl platform-idp create — POST a new PlatformIdPBindingRequest.
  • plexctl platform-idp list — GET every platform binding.

Unlike plexctl domain-idp, this family carries no --domain-id and no --primary flag: a shared binding has no owning Domain, and it has no Domain to be the default for. The just-in-time provisioned user joins the Domain selected at login, not the binding's.

Two safety contracts carry over from domain-idp:

  • Secret masking on output. Response fields whose JSON key matches (?i)secret|key|token are rewritten to *** in create and list output. Pass --reveal-secrets to opt out; in that case a one-line warning is written to stderr and the invocation is audit-logged.
  • File-only client secret on create. --client-secret MUST be passed as @<path> to a file holding the secret; an inline literal is rejected at parse time. The file content is read once and trimmed of trailing whitespace before being marshalled into the PlatformIdPBindingRequest.client_secret_ref field.

A platform binding's domain_id is null in the response. The default table renderer shows the domain column as platform to make the scope obvious at a glance.

Synopsis

shell
plexctl platform-idp create --client-id <ID> --client-secret @<PATH> \
                            --discovery-url <URL> --issuer <URL> --jit-policy allow|deny \
                            [--alias <slug>] \
                            [--required-acr <VALUE>]... [--required-amr <VALUE>]... \
                            [--claim-mapping plexsphere_claim=idp_claim]...
plexctl platform-idp list   [--reveal-secrets]

Invocation

plexctl platform-idp create

POSTs a PlatformIdPBindingRequest to /v1/admin/platform-idp, gated on the platform manage permission. The CLI validates --jit-policy, parses --claim-mapping plexsphere_claim=idp_claim pairs, and rejects an inline literal --client-secret.

Flags

FlagTypeRequiredDefaultDescription
--client-idstringyesOIDC client identifier.
--client-secretstring (@<path>)yesFile spec; inline literals rejected. The file's trimmed contents become client_secret_ref.
--discovery-urlURLyesOIDC discovery document URL.
--issuerURLyesOIDC issuer URL.
--jit-policyenumyesJust-in-time provisioning policy: allow or deny.
--aliasstringnoHuman-friendly handle for the binding, unique among active platform bindings (e.g. github). Normalised to lowercase kebab-case. A collision with another active platform alias surfaces as 409 alias-conflict.
--required-acrstring (repeatable)noRequired OIDC ACR value. May be passed multiple times.
--required-amrstring (repeatable)noRequired OIDC AMR value. May be passed multiple times.
--claim-mappingkey=value (repeatable)noMap a plexsphere claim to an IdP claim, e.g. email=preferred_email. The mappings apply uniformly to every Domain that resolves through this shared binding.

Persistent flags inherited from root: see plexctl.md. The root-level --reveal-secrets is honoured by create for masking fields on the IdPBindingResponse echoed back to stdout.

plexctl platform-idp list

GETs /v1/admin/platform-idp, gated on the platform read permission. Only platform bindings are returned; per-Domain bindings are reached via plexctl domain-idp list, which returns a Domain's effective set (its own bindings plus the platform ones). The response masks (?i)secret|key|token fields unless --reveal-secrets is passed.

Persistent flags inherited from root: see plexctl.md.

Exit codes

CodeReachable fromMeaning
0every subcommandSuccess.
1every subcommandRuntime / API error (non-2xx response, body parse error).
2every subcommandFlag-parse / misconfiguration (missing required flag, inline --client-secret without @ prefix, unknown --jit-policy, malformed --claim-mapping).
3every subcommandMissing or insecure credentials.
4every subcommandPermission denied (HTTP 403 from the server).

No platform-idp subcommand produces exit 64; the whole family is wired against the typed OpenAPI client.

Examples

Create a shared IdP binding (file-only client secret)

shell
echo -n 'super-secret-from-github' > /run/secrets/github-client.secret
chmod 0400 /run/secrets/github-client.secret

plexctl platform-idp create \
  --server "${PLEXSPHERE_URL}" \
  --client-id plexsphere-shared \
  --client-secret @/run/secrets/github-client.secret \
  --discovery-url https://github-idp.example.com/.well-known/openid-configuration \
  --issuer       https://github-idp.example.com/ \
  --jit-policy   allow \
  --alias        github

List platform bindings (default masked output)

shell
plexctl platform-idp list \
  --server "${PLEXSPHERE_URL}" \
  --output json
json
[
  {
    "id": "0190a8c1-...-a0a4",
    "domain_id": null,
    "client_id": "plexsphere-shared",
    "client_secret_ref": "***",
    "discovery_url": "https://github-idp.example.com/.well-known/openid-configuration",
    "issuer": "https://github-idp.example.com/",
    "jit_policy": "allow",
    "status": "active",
    "alias": "github"
  }
]

Log in two Domains through the same shared binding

shell
# Two different Domains, one shared IdP — each user lands in its Domain.
plexctl login --server "${PLEXSPHERE_URL}" --domain-id "${DOMAIN_A}"
plexctl login --server "${PLEXSPHERE_URL}" --domain-id "${DOMAIN_B}"

When a Domain owns no binding of its own, a Domain-only login resolves the shared platform binding from the Domain's effective set. When the Domain also owns a binding (or marks one primary), its own binding wins — the shared binding never out-ranks a Domain's own default.

Cross-references

  • ../../../api/openapi/plexsphere-v1.yaml — OpenAPI source of truth for /v1/admin/platform-idp and the PlatformIdPBindingRequest / IdPBindingResponse schemas.
  • domain-idp.md — the per-Domain IdP binding CLI surface, whose list returns a Domain's effective set.
  • ../../contexts/identity/idp.md — bounded-context reference: platform-scoped bindings, the just-in-time-into-login-Domain semantics, and the resolution precedence.
  • login.mdplexctl login, which resolves a shared binding for a Domain that owns none.
  • plexctl.md — root command reference (persistent flags, profile resolution, shared exit-code taxonomy).