Portal Community

Why Data Scoping Is Necessary

Hiding a widget does not prevent an API call from being made. If a widget makes a data request, that request returns whatever the server sends — regardless of UI visibility rules. Data security must be enforced at the server.

// WITHOUT data scoping:
// A viewer might modify the URL, reload the page,
// and the DataGrid would load all records — even from a "hidden" widget.

// WITH data scoping (correct approach):
// The data service enforces tenant + role filters on every query.
// Even if the request is made, only authorized records are returned.

How Data Scoping Works in App Studio

App Studio widgets load data through data services configured with the widget. The data service is responsible for applying role-based filters. App Studio passes the user's context (including roles) with every data request:

1
Widget requests data

The DataGrid, Form, or other data-bound widget calls its configured data source.

2
Context forwarded with request

App Studio includes the user's Passport JWT in the Authorization header of the API call. The server extracts tenantId, userId, and roles from the token.

3
Server applies role filters

The data service (e.g., a Flow Studio workflow or a backend API) applies tenant isolation and role-based data filters before returning records.

4
Filtered data returned

Only authorized records are included in the response. The widget displays what it receives.

Passing context to Data Sources

Use context.* tokens in widget data source params to pass the user's identity to backend filters:

// DataGrid — load only leads assigned to the current user (sales rep scope)
{
  "widgetId": "my-leads-grid",
  "type": "DataGrid",
  "config": {
    "dataSource": "GetLeads",
    "params": {
      "assignedUserId": "{{ context.userId }}",   // Server filters by this
      "tenantId": "{{ context.tenantId }}"
    }
  }
}

// For managers — load leads for their team
// The backend uses context.roles to determine if the user can see all leads
{
  "dataSource": "GetLeads",
  "params": {
    "requestingUserId": "{{ context.userId }}",
    "requestingUserRoles": "{{ context.roles.join(',') }}"
  }
}

Tenant Isolation

Tenant isolation is enforced at the data layer — always. The tenantId from the JWT is used by all data services to scope queries. App Studio never mixes data across tenants regardless of widget configuration.

// The platform guarantees: all data requests carry tenantId from the JWT.
// Data services MUST NOT use tenantId from query params alone —
// always validate against the JWT claim.
//
// This is enforced by the IAppStudioService architecture —
// the service layer always sources tenantId from the authenticated context.

Role-Based Column Visibility + Data Scoping Together

// Combining UI hiding (column visibility) with data scoping (server-side):
// Even if someone bypasses the column visibility in the UI,
// the server won't return the salary field for non-HR users.

// Widget config — hide salary column in the UI for non-HR
{
  "field": "salary",
  "header": "Salary",
  "visibilityExpression": "{{ context.roles.includes('hr') }}"
}

// Server-side data service — exclude salary from non-HR responses
// (This lives in the Flow workflow or backend API, not in App Studio config)
Defense in depth Use both widget visibility (client-side UX) and data scoping (server-side enforcement) together. Widget visibility improves UX; data scoping is the actual security control. Never rely on widget visibility alone for sensitive data.