Flow Studio
Permission Check Node
Asserting that a user holds a specific permission — the node succeeds silently if the check passes, and routes to the error port if not, blocking the workflow from proceeding.
Node Configuration
{
"nodeType": "PermissionCheck",
"name": "assertCanApprove",
"config": {
"userId": "$context.actorId",
"permission": "invoices:approve",
"resource": "$output.createInvoice.invoiceId",
"failMessage": "Actor does not have invoices:approve permission"
}
}
Configuration Fields
| Field | Type | Description |
|---|---|---|
userId | string / expr | The user ID to check. Commonly $context.actorId or $output.fetchApprover.userId. |
permission | string | The permission key to assert. Format: resource:action (e.g., invoices:approve). |
resource | string / expr | Optional resource ID for resource-scoped permission checks. If omitted, checks global permission. |
failMessage | string | The error message written to the error port output when the check fails. |
Routing Pattern
The PermissionCheckNode produces no data output — it is a gate. Connect the main port to the protected path and the error port to an access-denied handler:
PermissionCheckNode (assertCanApprove)
│
├── [main] ──→ Protected workflow steps
│
└── [error] ─→ SendEmail (access denied notification)
└──→ EndNode (terminate)
Error Port Output
{
"error": "Actor does not have invoices:approve permission",
"permission": "invoices:approve",
"userId": "usr-a1b2c3",
"resource": "inv-99887",
"checkedAt": "2026-05-25T10:00:00Z"
}
PermissionCheckExecutor
public class PermissionCheckExecutor : BaseNodeExecutor<PermissionCheckConfig>
{
protected override async Task<NodeExecutionResult> ExecuteAsync(
PermissionCheckConfig config,
NodeDataContext ctx,
CancellationToken ct)
{
var userId = _evaluator.Evaluate<string>(config.UserId, ctx);
var resource = config.Resource != null
? _evaluator.Evaluate<string>(config.Resource, ctx)
: null;
var hasPermission = await _passport.CheckPermissionAsync(new PermissionCheckRequest
{
UserId = userId,
Permission = config.Permission,
Resource = resource,
TenantId = ctx.TenantId
}, ct);
if (!hasPermission)
return NodeExecutionResult.Fail(new PermissionDeniedException(
config.FailMessage ?? $"Permission denied: {config.Permission}"));
return NodeExecutionResult.Success(null);
}
}
Defense in depth: The
PermissionCheckNode is a workflow-level gate — useful for business logic enforcement. It does not replace API-level authorization in the backend. Critical operations should be protected at both layers.