Credential Rotation
Rotate managed identity secrets without downtime — generate a new secret while the old one remains active, update all consumers to use the new secret, verify, then revoke the old secret.
The Dual-Key Rotation Pattern
Passport supports multiple active secrets per managed identity. This enables zero-downtime rotation: the old secret remains valid while you distribute the new secret to consumers, so there is no window where authentication fails.
Generate New Secret
Call POST .../credentials/secrets to generate a new secret. The old secret remains fully active — both secrets are valid simultaneously. Store the new secret in your secrets manager immediately.
Update Secrets Manager
Write the new secret value to your secrets manager (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault). Do not delete the old version yet — keep it accessible in case of rollback.
Deploy / Reload Consumers
Deploy updated configuration or trigger a secrets reload in each consuming service. Services that cache tokens will continue using cached tokens from the old secret until they expire (up to 1 hour). New token requests will use the new secret.
Verify New Secret Is Working
Monitor authentication events in the audit log. Confirm all consumers are successfully obtaining tokens using the new secret (look for secretId of the new secret in audit events). Allow at least one token lifetime (1 hour) to pass.
Revoke Old Secret
Call DELETE .../credentials/secrets/{oldSecretId} with reason rotation-complete. Any tokens still active from the old secret are immediately invalidated. New authentication attempts with the old secret will fail.
Rotation API Sequence
// Step 1: Generate new secret (old secret still active)
POST /passport/admin/managed-identities/{managedIdentityId}/credentials/secrets
Authorization: Bearer {admin-token}
Content-Type: application/json
{
"label": "rotation-2026-05"
}
// Response — store this immediately
{
"secretId": "sec-guid-NEW",
"clientSecret": "mics_sk_NEW_VALUE_yyyyyyyy",
"label": "rotation-2026-05",
"createdAt": "2026-05-25T10:00:00Z"
}
// Step 2: Update secrets manager (example: Azure Key Vault CLI)
// az keyvault secret set \
// --vault-name company-vault \
// --name payroll-scheduler-client-secret \
// --value "mics_sk_NEW_VALUE_yyyyyyyy"
// Step 3: Verify new secret works
POST /passport/token
Authorization: Basic base64("clientId:mics_sk_NEW_VALUE_yyyyyyyy")
grant_type=client_credentials
// Step 4: Revoke old secret
DELETE /passport/admin/managed-identities/{managedIdentityId}/credentials/secrets/sec-guid-OLD
Authorization: Bearer {admin-token}
Content-Type: application/json
{
"reason": "rotation-complete"
}
// Confirmation
{
"secretId": "sec-guid-OLD",
"revokedAt": "2026-05-25T11:00:00Z",
"reason": "rotation-complete"
}
Automated Rotation with Azure Key Vault
# Azure Key Vault automatic rotation via event trigger
# When Key Vault fires a "SecretNearExpiry" event, a function rotates the secret
[FunctionName("RotateManagedIdentitySecret")]
public async Task Run(
[EventGridTrigger] EventGridEvent eventGridEvent,
ILogger log)
{
var data = eventGridEvent.Data.ToObjectFromJson<SecretRotationEvent>();
var miId = Guid.Parse(data.Tags["managed-identity-id"]);
var label = $"rotation-{DateTime.UtcNow:yyyy-MM}";
// 1. Generate new secret
var newSecret = await _credentialService.GenerateSecretAsync(miId,
new GenerateSecretRequest { Label = label });
// 2. Update Key Vault with new version
await _keyVault.SetSecretAsync(data.SecretName, newSecret.ClientSecret);
// 3. Wait for propagation (give services time to pick up new version)
await Task.Delay(TimeSpan.FromMinutes(5));
// 4. Revoke old secret
var oldSecrets = await _credentialService.ListSecretsAsync(miId);
var oldSecret = oldSecrets.First(s => s.Label != label && s.IsActive);
await _credentialService.RevokeSecretAsync(miId, oldSecret.SecretId,
"automated-rotation");
log.LogInformation("Rotation complete for managed identity {MiId}", miId);
}
Emergency Rotation (Security Incident)
If you suspect a secret has been compromised, use the emergency rotation path — this immediately disables the managed identity (invalidating all active tokens), then re-enables it after generating a new secret:
// Emergency: disable immediately (revokes ALL tokens)
POST /passport/admin/managed-identities/{managedIdentityId}/disable
{
"reason": "security-incident",
"disabledBy": "security-team"
}
// Revoke all existing secrets
DELETE /passport/admin/managed-identities/{managedIdentityId}/credentials/secrets/{secretId1}
{ "reason": "security-incident" }
DELETE /passport/admin/managed-identities/{managedIdentityId}/credentials/secrets/{secretId2}
{ "reason": "security-incident" }
// Generate a new secret
POST /passport/admin/managed-identities/{managedIdentityId}/credentials/secrets
{ "label": "emergency-replacement" }
// Update secrets manager with new value, then re-enable
POST /passport/admin/managed-identities/{managedIdentityId}/enable
Rotation Checklist
| Step | Action | Verify |
|---|---|---|
| 1 | Generate new secret in Passport | New secretId visible in LIST .../secrets |
| 2 | Store new secret in secrets manager | New version accessible via secrets manager API |
| 3 | Deploy/reload consuming services | Services restarted or secrets refreshed |
| 4 | Monitor audit log for new secretId | Audit events show new secretId in auth records |
| 5 | Wait 1+ token lifetime (1 hour) | No active tokens from old secret remain |
| 6 | Revoke old secret | Old secretId shows isActive: false in LIST |
| 7 | Delete old secret version from secrets manager | Old version not accessible |
Always wait at least one full token lifetime (1 hour) after deploying the new secret before revoking the old one. If any service is still using a cached token obtained from the old secret, that token will be immediately invalidated when you revoke — causing unexpected authentication failures. The audit log shows which secretId was used for each token issuance.