Passport
Runtime Permission Checks
How execution nodes, services, and workflow orchestrators check permissions at runtime — using IPassportClient.CheckPermissionAsync() with resource-scoped evaluation.
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) { ... }