Portal Community

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.

AutomationWrong (too broad)Correct (least-privilege)
Monthly payroll runneradminpayroll-executor
ERP data syncmanagererp-reader, sync-writer
Slack notification senderusernotification-sender
Backup export jobadminbackup-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"]
}
Role Changes Take Effect at Next Token Request

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.