Skip to main content
Fine-grained access control lets you implement row-level security directly within your Malloy models. While resource hierarchy controls access at the project and package level (“can you see this package?”), fine-grained ACLs control access at the source level and filter which rows are visible (“which sources can you query, and which rows can you see?”).

Source-Level Authorization

The #authorize annotation controls who can access a source. This allows you to restrict which users or groups can query a source — even if do not have access to the package. The annotation takes a JavaScript boolean expression that determines whether access is granted. When a request attempts to access a source with an #authorize annotation, the Credible service evaluates the boolean expression. If the expression evaluates to true, access is granted; otherwise, an authorization error is returned. The Credible service provides two variables for authorization checks:
  • USER: A string containing the user ID associated with the request
  • JWT: A JSON object containing user attributes (see User Attributes below)
Additionally, the service provides a membership check function: isMember(USER, 'group_name'), which returns true if the user access to the specified group — directly or transitively.
// Any valid user can query this source, regardless of package access
# authorize ["true"]
source: public_airports is airports extend {
  dimension:
    has_control_tower is cntl_twr = 'Y'

  measure:
    airport_count is count()
}

// Only members of the '[email protected]' group can query this source
# authorize ["isMember(USER, '[email protected]')"]
source: admin_airports is airports extend {
  dimension:
    classified_status is status_code

  measure:
    total_budget is sum(annual_budget)
}

Combining with Row Filters

You can combine #authorize with a where: clause to both restrict access and filter which rows are visible:
// Only air traffic controllers can access this source,
// and they only see airports with control towers
# authorize ["isMember(USER, '[email protected]')"]
source: atc_airports is airports extend {
  where: cntl_twr = 'Y'

  measure:
    airport_count is count()
}

User Attributes

User attributes are values associated with a user—such as their region, tenant, or department. They enable dynamic authorization checks and row-level filtering based on who is querying the data.

How It Works

  1. Your service calls the Credible API to mint a JWT containing user attributes (e.g., region: 'west')
  2. The user includes this JWT when making queries
  3. The JWT’s attributes can be used in authorization checks or can be bound to a Malloy parameter using the #bind annotation.

Authorization with User Attributes

You can use JWT attributes directly in #authorize expressions to control source access:
// Only users with the 'admin' role can query this source
# authorize ["JWT['role'] === 'admin'"]
source: admin_airports is airports extend {
  dimension:
    classified_status is status_code

  measure:
    total_budget is sum(annual_budget)
}
When a user queries this source, the Credible service checks if their JWT contains role: 'admin'. If not, the request is denied.

Binding User Attributes to Parameters

The #bind annotation connects a JWT attribute to a source parameter. This parameter can then be used in a where: clause to automatically filter rows based on the user’s attributes.
// Bind the user's region from their JWT to the source parameter
# bind ["user_region:JWT['region']"]
source: regional_sales(user_region: string) is sales extend {
  where: sales_region = user_region

  dimension:
    region is sales_region
    product is product_name

  measure:
    total_revenue is sum(revenue)
    order_count is count()
}
When a user with region: 'west' in their JWT queries this source, the user_region parameter is automatically set to 'west', and they only see rows where sales_region = 'west'.

Example: Multi-Tenant Embedded Analytics

If you’re building an analytics dashboard for your customers (e.g., using the Publisher SDK), you can use attribute binding to ensure each customer only sees their own data:
// Each customer only sees data for their tenant
# bind ["tenant:JWT['tenant_id']"]
source: tenant_data(tenant: string) is app_data extend {
  where: customer_id = tenant

  dimension:
    user_name is name
    user_email is email

  measure:
    active_users is count(distinct user_id)
}
When you mint a JWT for a customer with tenant_id: 'acme', they only see rows where customer_id = 'acme'. This ensures complete data isolation between tenants without requiring separate data sources.
Have custom access control requirements? Contact us to discuss your use case.

Next Steps