Portal Community

IPassportClient.CheckPermissionAsync()

public interface IPassportClient
{
    /// <summary>
    /// Check if the user identified by userId has the specified permission
    /// on the optional resource. Returns true = Allow, false = Deny.
    ///
    /// Permission: "workflow.execute", "form.submit", etc.
    /// ResourceId: optional GUID of the specific resource to check (enables resource-level policies).
    /// </summary>
    Task<bool> CheckPermissionAsync(
        string userId,
        string permission,
        string? resourceId = null,
        CancellationToken ct = default);

    /// <summary>
    /// Bulk permission check — evaluates multiple permissions in one call.
    /// More efficient than multiple individual calls.
    /// </summary>
    Task<IReadOnlyDictionary<string, bool>> CheckPermissionsAsync(
        string userId,
        IReadOnlyList<string> permissions,
        string? resourceId = null,
        CancellationToken ct = default);
}

Usage in Execution Nodes

// In a BaseNodeExecutor — check permission before executing
public abstract class SecureBaseNodeExecutor : BaseNodeExecutor
{
    private readonly IPassportClient _passport;

    protected async Task<bool> RequirePermissionAsync(
        string permission,
        IDInfo actor,
        string? resourceId = null)
    {
        var allowed = await _passport.CheckPermissionAsync(
            userId:     actor.UserId,
            permission: permission,
            resourceId: resourceId);

        if (!allowed)
        {
            Logger.LogWarning(
                "Permission denied: user={UserId} permission={Permission} resource={ResourceId}",
                actor.UserId, permission, resourceId);
        }

        return allowed;
    }
}

// In a workflow execution node
public sealed class PayrollWorkflowNode : SecureBaseNodeExecutor
{
    public override async Task ExecuteAsync(NodeExecutionContext ctx)
    {
        var allowed = await RequirePermissionAsync(
            permission: "workflow.initiate",
            actor:      ctx.Actor,
            resourceId: ctx.WorkflowDefinitionId.ToString());

        if (!allowed)
        {
            ctx.SetOutput("result", "access-denied");
            return;
        }

        // ... execute payroll logic
    }
}

ProcessAccessGuard

ProcessAccessGuard is the IAM integration layer in Flow Studio's execution engine. Every workflow execution request passes through it before any nodes are invoked:

// ProcessAccessGuard checks before workflow execution begins
public sealed class ProcessAccessGuard(IPassportClient passport)
{
    public async Task<AccessResult> GuardWorkflowExecutionAsync(
        WorkflowExecutionRequest request,
        CancellationToken ct)
    {
        // Check initiation permission
        var canInitiate = await passport.CheckPermissionAsync(
            userId:     request.Actor.UserId,
            permission: "workflow.initiate",
            resourceId: request.WorkflowId.ToString(),
            ct:         ct);

        if (!canInitiate)
            return AccessResult.Deny("workflow.initiate", request.WorkflowId);

        // Check tenant isolation
        if (request.Actor.TenantId != request.WorkflowTenantId)
            return AccessResult.Deny("cross-tenant-access", request.WorkflowId);

        return AccessResult.Allow();
    }
}

Bulk Permission Check for UI

// Check multiple permissions for UI rendering (show/hide elements)
var permissions = await _passport.CheckPermissionsAsync(
    userId: idInfo.UserId,
    permissions: [
        "workflow.design",
        "workflow.initiate",
        "form.create",
        "iam.user.manage",
        "audit.read"
    ]);

// permissions = {
//   "workflow.design":  true,
//   "workflow.initiate": true,
//   "form.create":       false,
//   "iam.user.manage":   false,
//   "audit.read":        true
// }

// Use in React props
return <NavBar
  showDesigner={permissions["workflow.design"]}
  showUsers={permissions["iam.user.manage"]}
  showAudit={permissions["audit.read"]}
/>;

API Endpoint Guard (Attribute)

// Custom authorization attribute using Passport IAM
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class RequirePassportPermissionAttribute(string permission, string? resourceParam = null)
    : Attribute, IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext ctx, ActionExecutionDelegate next)
    {
        var passport = ctx.HttpContext.RequestServices.GetRequiredService<IPassportClient>();
        var idInfo   = ctx.HttpContext.GetIDInfo();

        if (idInfo is null)
        {
            ctx.Result = new UnauthorizedResult();
            return;
        }

        string? resourceId = resourceParam is not null
            ? ctx.ActionArguments.GetValueOrDefault(resourceParam)?.ToString()
            : null;

        var allowed = await passport.CheckPermissionAsync(idInfo.UserId, permission, resourceId);
        if (!allowed)
        {
            ctx.Result = new ForbidResult();
            return;
        }

        await next();
    }
}

// Usage on a controller action
[HttpPost("{workflowId}/execute")]
[RequirePassportPermission("workflow.initiate", resourceParam: "workflowId")]
public Task<IActionResult> ExecuteWorkflow(Guid workflowId) { ... }