Appearance
Manage IdP bindings
An IdP binding tells a Domain which upstream OIDC provider to drive for sign-in. plexctl domain-idp wraps /v1/admin/idp.
Prerequisites
- An authenticated session — see Log in with plexctl.
- The Domain UUID and the OIDC client credentials from your provider.
- The client secret saved to a file (inline literals are rejected).
Steps
Create a binding
The client secret is passed by reference (@<path>), so stage it in a private scratch file first. Set a tight umask so the file is created 0600, and use mktemp rather than a predictable /tmp/idp-secret path:
shell
umask 077
secret_file="$(mktemp)"
printf '%s' "$OIDC_CLIENT_SECRET" > "$secret_file"
plexctl domain-idp create \
--domain-id <domain-uuid> \
--client-id plexsphere \
--client-secret "@${secret_file}" \
--discovery-url https://idp.example.com/.well-known/openid-configuration \
--issuer https://idp.example.com \
--jit-policy allow \
--claim-mapping email=preferred_email
# ID DOMAIN CLIENT_ID ISSUER JIT_POLICY STATUS CLIENT_SECRET_REF
# … … plexsphere https://idp.example.com allow active ***--client-secret must be @<path>; the file is read once and trimmed. Secret-shaped response fields are masked unless you pass --reveal-secrets (audit-logged).
Remove the secret file. The scratch file holds the plaintext client secret. Even at
0600it is readable by your own other processes and survives until you delete it — it does not vanish when the shell exits. Remove it as soon as the binding is created:shellrm -f "$secret_file"
List bindings
shell
plexctl domain-idp list --domain-id <domain-uuid>Get or update a binding
shell
plexctl domain-idp get --id <binding-uuid>
plexctl domain-idp update --id <binding-uuid> --jit-policy denyupdate is a partial PATCH: only the flags you set (--jit-policy, --discovery-url, --required-acr, --required-amr, --claim-mapping, --alias, --primary) are forwarded. Status is changed through enable/disable, not update.
Name a binding and pick a primary
Give a binding a human-friendly alias so it can be addressed by name, and mark one binding per Domain as the primary so a Domain-only login resolves to it even when the Domain has several bindings:
shell
# At create time.
plexctl domain-idp create \
--domain-id <domain-uuid> \
--client-id plexsphere \
--client-secret "@${secret_file}" \
--discovery-url https://idp.example.com/.well-known/openid-configuration \
--issuer https://idp.example.com \
--jit-policy allow \
--alias github \
--primary
# Or promote an existing binding later.
plexctl domain-idp update --id <binding-uuid> --primaryThe alias is normalised to lowercase kebab-case, so --alias GitHub stores github. It is unique per Domain among active bindings; a collision returns 409 alias-conflict. Only one active binding per Domain may be primary — promoting a second while one is already primary returns 409 primary-conflict, so demote the current primary first with --primary=false. Clear an alias with --alias "".
With an alias and a primary set, you can log in by Domain alone or pin the binding by name:
shell
plexctl login --domain-id <domain-uuid> # resolves the primary
plexctl login --domain-id <domain-uuid> --idp-binding github # resolves by aliasShare one IdP across Domains
When the same upstream IdP serves many tenants, register it once as a platform-scoped (shared) binding instead of duplicating a binding per Domain. A platform binding is owned by no Domain and is usable by any Domain; the just-in-time provisioned user joins the Domain selected at login, not the binding's.
shell
# Register the shared binding once (platform-scoped: no --domain-id, no --primary).
plexctl platform-idp create \
--client-id plexsphere-shared \
--client-secret "@${secret_file}" \
--discovery-url https://github-idp.example.com/.well-known/openid-configuration \
--issuer https://github-idp.example.com \
--jit-policy allow \
--alias github
# List the platform bindings (domain_id renders as `platform`).
plexctl platform-idp list
# Each Domain that owns no binding resolves the shared one on a
# Domain-only login; the user lands in the login Domain.
plexctl login --domain-id <domain-a-uuid>
plexctl login --domain-id <domain-b-uuid>Platform create and list gate on the platform manage / read permissions, distinct from the per-Domain domain#manage / domain#read gates. A Domain's own primary or sole binding still wins over a shared one — the shared binding fills in only when the Domain owns nothing applicable. The per-Domain plexctl domain-idp list shows a Domain's effective set (its own bindings plus the platform ones).
A shared binding is administered through the same by-id commands a Domain binding is (domain-idp enable / disable / delete and the by-id read), and those follow the binding's own scope: acting on a shared binding needs the platform permission, not one on the Domain you happened to list it from. So a Domain admin who sees a shared binding in domain-idp list gets 403 when addressing it by id — that binding is the platform's to manage. Use plexctl platform-idp with a platform grant instead.
Enable, disable, or delete
shell
plexctl domain-idp enable --id <binding-uuid>
plexctl domain-idp disable --id <binding-uuid>
plexctl domain-idp delete --id <binding-uuid>Verification
shell
plexctl domain-idp list --domain-id <domain-uuid> --output json \
| jq '.[] | {id, issuer, status}'
# {
# "id": "…",
# "issuer": "https://idp.example.com",
# "status": "active"
# }The new binding appears with status: "active".
See also
- Log in with plexctl — uses the binding ID.
../../reference/cli/plexctl/domain-idp.md— every flag and exit code.../../contexts/identity/idp.md— the IdP binding model.