Managed Identities Overview
Managed identities are non-human Passport principals for automated processes — scheduled workflows, system integrations, and background jobs — with full role-based access control and audit logging.
Why Not Use User Accounts for Automation?
Using a personal user account for automation creates serious operational and security risks:
Account Lifecycle
When the employee leaves, their account is disabled — breaking all automations that depend on it, often at 2 AM on a Friday.
Password Rotation
User passwords expire. Automations fail silently until someone discovers the expired password in a production incident.
Audit Confusion
Audit logs show "Jane Smith ran 500 payroll jobs at 3 AM". Automations obscure the human actions in security logs.
Excess Permissions
User accounts have all permissions granted for their human role — far more than the automation needs. Violates least privilege.
What Is a Managed Identity?
A managed identity is a Passport principal that represents an automated process or system integration rather than a human user. It has:
- A stable
clientId(GUID) that identifies the system - A
clientSecretthat authenticates it — rotatable without downtime - Roles and permissions assigned explicitly — only what the automation needs
- Full audit trail — all actions appear in logs attributed to the managed identity, not a person
- No MFA, no session, no login form — pure API authentication
IManagedIdentityService
namespace BizFirst.Essentials.Passport.ManagedIdentities;
public interface IManagedIdentityService
{
Task<ManagedIdentity> CreateAsync(
CreateManagedIdentityRequest request,
CancellationToken ct = default);
Task<ManagedIdentity?> GetAsync(
Guid managedIdentityId,
CancellationToken ct = default);
Task<IReadOnlyList<ManagedIdentity>> ListAsync(
string tenantId,
CancellationToken ct = default);
Task DisableAsync(
Guid managedIdentityId,
CancellationToken ct = default);
Task DeleteAsync(
Guid managedIdentityId,
CancellationToken ct = default);
}
public class ManagedIdentity
{
public required Guid Id { get; init; }
public required string ClientId { get; init; } // for OAuth client credentials flow
public required string TenantId { get; init; }
public required string Name { get; init; }
public string? Description { get; init; }
public bool IsEnabled { get; init; } = true;
public DateTimeOffset CreatedAt { get; init; }
public DateTimeOffset? LastUsedAt { get; init; }
public IReadOnlyList<string> Roles { get; init; } = [];
}
Authentication Flow
// OAuth 2.0 Client Credentials Flow
POST /passport/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64("clientId:clientSecret")
grant_type=client_credentials
&scope=openid%20roles
// Response
{
"access_token": "eyJhbGciOiJSUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 3600 // 1 hour
// No refresh_token — each call uses client credentials
}
// Token payload shows it is a managed identity
{
"sub": "managed-identity-client-id",
"managed_identity_id": "managed-identity-guid",
"is_service_account": true,
"tenant_id": "tenant-abc",
"roles": ["payroll-executor"],
"iss": "https://passport.bizfirst.ai",
"aud": "bizfirstgo-api"
}
Key Differences from User Accounts
| Aspect | User Account | Managed Identity |
|---|---|---|
| Authentication | Password + optional MFA | clientId + clientSecret |
| Session | Long-lived session cookie | No session — per-call authentication |
| Token type | Access + refresh + id tokens | Access token only (no refresh) |
| Token lifetime | 15 min access / 7 day refresh | 1 hour access |
| MFA | Optional or required | Not applicable |
| Password expiry | Yes (configurable) | No — secrets have no expiry unless explicitly rotated |
| Audit attribution | Human name + email | Managed identity name + clientId |