Using in Workflow Execution
The WorkflowScheduler mints a managed identity token before each scheduled execution — all workflow actions run under the managed identity's identity, giving audit logs a clear non-human actor for every automated step.
WorkflowScheduler Integration
When a scheduled workflow trigger fires, the WorkflowScheduler follows this sequence before dispatching the execution:
Resolve Managed Identity
The scheduler reads the managedIdentityId from the schedule configuration. It looks up the identity to verify it is enabled and retrieves its clientId.
Retrieve Credential from Vault
The clientSecret is not stored by the scheduler. It retrieves the secret from the configured secrets manager (Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault) using the secret reference stored in the schedule config.
Mint Execution Token
The scheduler performs the OAuth client credentials flow against POST /passport/token, obtaining a fresh access token. This token has a 1-hour lifetime — more than enough for any single workflow execution.
Inject IDInfo into Execution Context
The token is exchanged for an IDInfo struct and injected into the workflow execution context. Every node in the workflow receives this IDInfo as its actor identity.
Execute Workflow
The workflow runs. All API calls made by execution nodes carry the managed identity's token. All audit log entries reference the managed identity, not a human user.
Schedule Configuration
// Workflow schedule with managed identity reference
POST /flowstudio/api/schedules
Authorization: Bearer {admin-token}
Content-Type: application/json
{
"workflowId": "wf-monthly-payroll",
"name": "Monthly Payroll Run",
"cronExpression": "0 6 25 * *", // 06:00 on the 25th of every month
"timezone": "UTC",
"managedIdentityId": "mi-guid-1234",
"secretReference": {
"provider": "azure-key-vault",
"vaultUri": "https://company-vault.vault.azure.net/",
"secretName": "payroll-scheduler-client-secret"
},
"inputData": {
"payrollMonth": "{{schedule.triggerMonth}}"
},
"retryPolicy": {
"maxAttempts": 3,
"backoffSeconds": 60
}
}
WorkflowScheduler Code Pattern
public class WorkflowScheduler : IWorkflowScheduler
{
private readonly IManagedIdentityService _identityService;
private readonly ISecretsManager _secrets;
private readonly IPassportClient _passport;
private readonly IWorkflowExecutionService _executor;
public async Task TriggerAsync(
ScheduleConfig schedule,
CancellationToken ct)
{
// 1. Verify managed identity is active
var identity = await _identityService.GetAsync(
schedule.ManagedIdentityId, ct)
?? throw new InvalidOperationException(
$"Managed identity {schedule.ManagedIdentityId} not found");
if (!identity.IsEnabled)
throw new InvalidOperationException(
$"Managed identity '{identity.Name}' is disabled");
// 2. Retrieve secret from secrets manager
var clientSecret = await _secrets.GetSecretAsync(
schedule.SecretReference, ct);
// 3. Mint a short-lived token
var tokenResponse = await _passport.ClientCredentialsAsync(
identity.ClientId, clientSecret, ct);
// 4. Build IDInfo from the token
var actorContext = IDInfo.FromJwt(tokenResponse.AccessToken);
// 5. Dispatch the workflow execution
await _executor.ExecuteAsync(new WorkflowExecutionRequest
{
WorkflowId = schedule.WorkflowId,
ActorContext = actorContext,
InputData = schedule.InputData,
TriggeredBy = $"schedule:{schedule.Name}"
}, ct);
}
}
Accessing Actor Identity Within a Node
// Inside any execution node — access the actor that triggered this workflow
public class PayrollCalculationNode : BaseNodeExecutor
{
protected override async Task<NodeExecutionResult> ExecuteAsync(
NodeExecutionContext context,
CancellationToken ct)
{
// The IDInfo of the scheduling managed identity
var actor = context.ActorContext;
_logger.LogInformation(
"Payroll calculation triggered by {Actor} (service account: {IsService})",
actor.UserId,
actor.IsServiceAccount);
// All outbound API calls from within the node automatically carry
// the managed identity's token — no extra configuration needed
var payrollData = await _payrollService.CalculateAsync(
context.TenantId,
ct);
return NodeExecutionResult.Success(payrollData);
}
}
ProcessAccessGuard for Managed Identities
Before the scheduler can launch a workflow, ProcessAccessGuard checks that the managed identity has the workflow.execute permission and that the specific workflow's resource policy does not deny the managed identity. If either check fails, the schedule fires but the execution is rejected with a 403 — this appears in the audit log as an authorization failure.
// ProcessAccessGuard checks — same pipeline for human and managed identity actors
public class ProcessAccessGuard : IProcessAccessGuard
{
public async Task<AccessDecision> CheckWorkflowExecutionAsync(
IDInfo actor,
string workflowId,
CancellationToken ct)
{
// Step 1: Role check — does the identity have workflow.execute?
if (!actor.Roles.Any(r => _rolePermissions[r].Contains("workflow.execute")))
return AccessDecision.Deny("Missing workflow.execute permission");
// Step 2: Resource policy check
var policy = await _policyStore.GetWorkflowPolicyAsync(workflowId, ct);
if (policy is not null)
{
var result = _policyEngine.Evaluate(actor, policy);
if (result.Effect == PolicyEffect.Deny)
return AccessDecision.Deny(result.Reason);
}
return AccessDecision.Allow();
}
}
The managed identity token issued at workflow start has a 1-hour lifetime. For workflows that run longer than 1 hour, the execution engine automatically refreshes the token using the same clientId + clientSecret pair before each node execution. The secret reference must remain valid for the duration of the workflow.