Portal Community

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.

1

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.

2

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.

3

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.

4

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.

5

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

StepActionVerify
1Generate new secret in PassportNew secretId visible in LIST .../secrets
2Store new secret in secrets managerNew version accessible via secrets manager API
3Deploy/reload consuming servicesServices restarted or secrets refreshed
4Monitor audit log for new secretIdAudit events show new secretId in auth records
5Wait 1+ token lifetime (1 hour)No active tokens from old secret remain
6Revoke old secretOld secretId shows isActive: false in LIST
7Delete old secret version from secrets managerOld version not accessible
Never Skip Verification Before Revoking

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.