Assigning Roles and Permissions
Managed identities use the same RBAC model as human users — assign only the roles the automation needs, apply least-privilege, and avoid broad roles like admin for service accounts.
Role Assignment API
// Assign roles to a managed identity
PUT /passport/admin/managed-identities/{managedIdentityId}/roles
Authorization: Bearer {admin-token}
Content-Type: application/json
{
"roles": ["payroll-executor", "report-reader"]
}
// Response
{
"managedIdentityId": "mi-guid-1234",
"roles": ["payroll-executor", "report-reader"],
"updatedAt": "2026-05-25T10:00:00Z"
}
// Add a single role without replacing existing ones
POST /passport/admin/managed-identities/{managedIdentityId}/roles/{roleName}
Authorization: Bearer {admin-token}
// Remove a single role
DELETE /passport/admin/managed-identities/{managedIdentityId}/roles/{roleName}
Authorization: Bearer {admin-token}
Roles in the JWT Token
When a managed identity authenticates via the client credentials flow, the issued JWT includes all assigned roles in the roles claim. The token is structurally identical to a human user's access token, except for the is_service_account: true flag:
// JWT payload for a managed identity token
{
"sub": "mi-payroll-scheduler-clientid",
"managed_identity_id": "mi-guid-1234",
"is_service_account": true,
"tenant_id": "tenant-abc",
"roles": ["payroll-executor", "report-reader"],
"iss": "https://passport.bizfirst.ai",
"aud": "bizfirstgo-api",
"iat": 1716631200,
"exp": 1716634800 // 1 hour lifetime
}
// BizFirstGO IDInfo populated from this token:
IDInfo {
UserId = "mi-payroll-scheduler-clientid", // sub claim
TenantId = "tenant-abc",
Email = "", // no email for service accounts
DisplayName = "payroll-scheduler", // name claim
Roles = ["payroll-executor", "report-reader"],
IsServiceAccount = true,
ManagedIdentityId = "mi-guid-1234"
}
Least-Privilege Role Design
Design dedicated roles for each automation rather than reusing human roles. The payroll scheduler needs to execute workflows and read reports — it should not inherit the full manager role that includes user management permissions.
| Automation | Wrong (too broad) | Correct (least-privilege) |
|---|---|---|
| Monthly payroll runner | admin | payroll-executor |
| ERP data sync | manager | erp-reader, sync-writer |
| Slack notification sender | user | notification-sender |
| Backup export job | admin | backup-reader |
Custom Role Definitions for Service Accounts
// Define a purpose-specific role for automation
POST /passport/admin/roles
Authorization: Bearer {admin-token}
Content-Type: application/json
{
"name": "payroll-executor",
"description": "Can execute payroll workflows and read payroll reports",
"permissions": [
"workflow.execute",
"payroll.read",
"payroll.run",
"report.payroll.read"
],
"isServiceAccountRole": true // advisory — marks role as intended for automation
}
// Then assign to the managed identity
PUT /passport/admin/managed-identities/{managedIdentityId}/roles
{
"roles": ["payroll-executor"]
}
Checking Permissions in Application Code
Application code uses the same permission check API regardless of whether the caller is a human user or managed identity. The IDInfo.IsServiceAccount flag is available if the application needs to apply different logic for automation actors:
// In a BizFirstGO controller or service
public class PayrollController : ControllerBase
{
private readonly IPassportClient _passport;
[HttpPost("run")]
public async Task<IActionResult> RunPayroll(
[FromServices] IDInfo caller,
CancellationToken ct)
{
// Check permission — same call for human or managed identity
var allowed = await _passport.CheckPermissionAsync(
caller, "payroll.run", ct);
if (!allowed)
return Forbid();
// Optional: log whether this is an automated run
if (caller.IsServiceAccount)
{
_logger.LogInformation(
"Payroll run triggered by managed identity {ManagedIdentityId}",
caller.ManagedIdentityId);
}
// ...execute payroll
}
}
Role Precedence and Deny Rules
Managed identities participate in the same IAM policy evaluation pipeline as human users. Resource-level deny rules apply to managed identities. If a resource policy denies payroll.run for all principals except those with a specific tag, the managed identity will be denied even if it has the payroll-executor role.
// Resource policy that restricts payroll execution to working hours
// This applies to managed identities as well as human users
{
"resourceType": "workflow",
"resourceId": "wf-monthly-payroll",
"conditions": [
{
"effect": "Deny",
"condition": "hour(now()) < 6 || hour(now()) >= 22",
"message": "Payroll workflow cannot run outside 06:00-22:00 UTC"
}
]
}
// Exception: tag the managed identity to bypass time restriction
// Requires the resource policy to explicitly check for the tag
{
"effect": "Deny",
"condition": "hour(now()) < 6 || hour(now()) >= 22",
"exceptions": ["tag:scheduled-automation"]
}
When you add or remove roles from a managed identity, the change is not immediately reflected in existing tokens. A managed identity's access token is valid for up to 1 hour. If you need to revoke access immediately, disable the managed identity — this invalidates all active tokens and prevents new token issuance.