Appearance
🛡️ adminadmin@example.com · Domain admin in Acme Corp
Chapter 7 — Machine identity
No browser this time
Every chapter so far leaned on a device-code sign-in — a human, a browser, an approval click.
A CI pipeline has none of those. This chapter is the non-interactive counterpart, and it runs in the svc-demo Domain, which seeds no human users at all.
bash
CLIENT_ID=svc-deployer
CLIENT_SECRET=svc-deployer-demo-secret🤖 machinesvc-deployer · a service identity in svc-demo
Authenticate the machine
bash
TOKEN=$(plexctl identity-tokens issue-service \
--client-id "$CLIENT_ID" \
--grant-type client_credentials \
--client-secret "$CLIENT_SECRET" \
--output json | jq -r '.access_token')
printf '%s' "$TOKEN" > /tmp/service.token
test -s /tmp/service.token && echo "machine token captured"The client_credentials grant. One request, no redirect, no browser.
The platform knows it is a machine
bash
plexctl whoami --token-file /tmp/service.tokentext
PRINCIPAL SCOPE SUBJECT DOMAIN ACR AMR
service domain 019ecc69-1a2b-7c3d-8e4f-0a1b2c3d4e5f 019ecc60-2b3c-7d4e-8f50-1a2b3c4d5e6f - -PRINCIPAL = service, not user.
ACR and AMR are empty — those describe how a human authenticated, and this one did not. It presented a secret.
bash
SERVICE_ID=$(plexctl whoami --token-file /tmp/service.token --output json \
| jq -r '.subject')What the machine holds
bash
plexctl identity-tokens list --token-file /tmp/service.tokentext
ID ENV CREATED_AT EXPIRES_AT LAST_USED_AT ROTATED_AT
019ecc69-3c4d-7e5f-9061-2b3c4d5e6f70 live 2026-06-15T18:40:11Z 2026-09-13T18:40:11Z - -bash
TOKEN_ID=$(plexctl identity-tokens list --token-file /tmp/service.token \
--output json | jq -r '.items[0].id')The wall
bash
plexctl identity-tokens issue \
--identity-ref "service:$SERVICE_ID" \
--env-prefix dev \
--token-file /tmp/service.tokentext
plexctl: Forbidden: a caller authenticated via an API token cannot mint another API token; sign in interactively to issue tokensA token cannot mint another token.
Otherwise one leaked credential spawns an unbounded family of credentials, each of which spawns more, and revocation becomes a guessing game.
Minting is interactive and human-only. A machine's credential lifecycle is rotation.
Rotation has an overlap window
bash
plexctl identity-tokens rotate \
--id "$TOKEN_ID" \
--token-file /tmp/service.tokentext
# WARNING: this is the only time this plaintext will be displayed
psk_live_4mN8pQ2rX7vK9wL3tY6bC1dF5gH0jS2aZ8eW
id: 019ecc69-4d5e-7f60-8172-3c4d5e6f7081
env_prefix: live
expires_at: 2026-09-13T18:42:33Z
rotation_deadline: 2026-06-17T18:42:33ZLook at rotation_deadline: 48 hours in which both plaintexts are honoured.
A fleet picks up the new secret on its own schedule. No coordinated restart, no outage window. That overlap is what makes unattended rotation safe.
Decommission
bash
plexctl identity-tokens delete \
--id "$TOKEN_ID" \
--token-file /tmp/service.token
rm -f /tmp/service.tokenThe next request presenting that token is rejected.
The service identity itself is untouched — it can authenticate again with issue-service whenever it needs a fresh token, exactly as a person can log in again after logging out.