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 applies an access filter so that specific users or groups can query a source—even if they have access to the package.
// Anyone with package access can query this source
# authorize public
source: public_airports is airports extend {
  dimension:
    has_control_tower is cntl_twr = 'Y'

  measure:
    airport_count is count()
}

// Only members of the admin group can query this source
# authorize ["group:[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 further restrict which rows are visible:
// Air traffic controllers only see airports with control towers
# authorize ["group:[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—like their region, tenant, or department. They enable dynamic row filtering based on who is querying the data. This is similar to Looker’s user attributes concept. The #bind annotation binds a user attribute from the JWT to a source parameter, which is then used in a where: filter.

How It Works

  1. Your service calls the Credible API to mint a JWT with user attributes (e.g., region='west')
  2. User passes the JWT when querying
  3. Credible decodes the JWT, extracts attributes, and binds them to source parameters
  4. Query executes with automatic row filtering based on the user’s attributes

Example: Regional Sales Data

# bind ["user_region: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, they only see rows where sales_region = 'west'.

Example: Embedded Analytics

If you’re building an analytics dashboard for your customers (e.g., using the Publisher SDK), each customer should only see their own data:
# bind ["tenant: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'.
Have custom access control requirements? Contact us to discuss your use case.

Next Steps