Portal Community

WorkflowScheduler Integration

When a scheduled workflow trigger fires, the WorkflowScheduler follows this sequence before dispatching the execution:

1

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.

2

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.

3

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.

4

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.

5

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();
    }
}
Token Lifetime and Long-Running Workflows

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.