Rotate an application credential
By the end of this guide, your application authenticates to the VTA with a freshly minted credential, and the old one can no longer start a session. Revoke its sessions as well if you need it cut off before its current access token expires.
This guide re-runs the same sealed-transfer bootstrap used to provision the application in the first place, issuing a new DID and private key for the same context and role. The context, its keys, and the application’s configuration are untouched. For how that delivery works, see Sealed transfer.
A credential that has leaked stays valid until you replace it and remove the old DID from the access control list. Rotation is also the routine answer to a credential reaching the end of its planned lifetime, rather than waiting for it to fail in production.
Use this guide when:
- An application credential has been exposed, or you suspect it has.
- A credential is due for rotation under your own key-rotation policy.
- A service is being handed to a different team and should not keep the old identity.
You do not need this guide to rotate an admin DID bound to an integration such as a mediator or WebVH host; see Rotate an integration’s admin DID instead.
Prerequisites
A running VTA. See Quickstart.
Install Rust 1.95 or later on your machine (required for compiling the Personal Network Manager (PNM) CLI).
pnminstalled and connected to the VTA.cargo install pnm-cli@0.23.1 --locked --registry crates-ioAdmin access to the context the credential is scoped to.
The old credential’s DID, visible via
pnm acl list --context <context-id>.
Step 1: Generate a new bootstrap request
The three commands below deliver the new credential through sealed transfer, with the work split between two roles. One person on one machine can play both:
- Recipient: the machine where the credential will be used.
pnm bootstrap requestcreates a one-time key pair, keeps the private key in PNM’s local configuration directory, and writes only the public key, a nonce, and an optional label to the request file. The recipient later runspnm bootstrap openon the same machine, since only that private key can open the bundle. - Producer: an administrator whose PNM is connected to the VTA.
pnm auth-credential createcreates the new DID, adds it to the context’s access control list (ACL), seals its credential to the public key in the request file, and prints a SHA-256 digest of the sealed bundle.
The digest proves that the bundle the recipient opens is the one the producer sealed, because pnm bootstrap open --expect-digest refuses any other bundle. When the roles run on separate machines, move the request file to the producer over a channel you trust, since the producer seals the credential to whichever public key that file carries. The bundle can travel back over any channel, provided the digest reaches the recipient separately over a channel you trust, for example read out over a call.
Here the recipient is the machine that will hold the rotated credential, so run this step and Step 3 there.
pnm bootstrap request --out rotation-request.jsonStep 2: Create the new app credential
Note the SHA-256 digest printed to stderr. You need it in Step 3.
pnm auth-credential create \
--role application \
--contexts <context-id> \
--label "<your-app>-rotated" \
--recipient rotation-request.json > rotation-bundle.txtStep 3: Open the bundle
pnm bootstrap open \
--bundle rotation-bundle.txt \
--expect-digest <digest-from-step-2> \
--out credential-new.jsonThe summary pnm bootstrap open prints includes the new credential’s DID on its DID: line. Note it: it is the <new-did> you check in Test 1.
Step 4: Update your secrets manager
Replace the existing credential.json secret with the contents of credential-new.json. Restart the service to load the new credential.
Delete rotation-request.json and rotation-bundle.txt after this step. They are single-use, and neither should reach source control: the bundle carries HPKE-sealed key material, and the request file is bound to the ephemeral seed that can open it.
Step 5: Revoke the old credential
Run this once the service is authenticating with the new credential, confirmed below. The old DID stays active until you remove it. If the credential was compromised, treat this as urgent rather than routine.
# Find the old DID if you do not have it recorded
pnm acl list --context <context-id>
# Revoke it
pnm acl delete <old-did>The old DID can no longer refresh, and an access token it already holds keeps working until it expires, which is 15 minutes by default (see session lifetime). To cut it off at once, revoke its sessions as described in Grant and revoke access.
Confirm
Test 1: the service authenticates with the new credential
Trigger a request from the service that exercises the VTA, then check the new DID holds the role and context you expect:
pnm acl get <new-did>Name: <your-app>-rotated
DID: did:key:z6Mk...
Role: application
Contexts: my-app
Capabilities: (none granted by name — everything `application` allows)
Created At: 1789525447
Created By: did:key:z6MkAdmin...Run this before Step 5. Revoking the old credential while the new one is not yet working takes the service offline.
Test 2: the old DID is gone from the ACL
pnm acl list --context <context-id>The old DID is absent and the new one is present. A token the old DID already holds keeps working until it expires, 15 minutes by default.
Next steps
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.