Data modeling
Row level security

Row-level security

For customer-facing analytics, each end-user should only see the data they’re allowed to see and nothing more. That’s why row-level security is a first-class citizen in Embeddable.

Security Tokens and Security Context

Embeddable dashboards are embedded in your site via an HTML web component:

<em-beddable 
  token="eyJhbGciOiJIUzI..."
/>
  • The token parameter (a security token) must be retrieved server-side from our Tokens API each time a user accesses your Embeddable dashboard.

  • When requesting this security token, you also send a security context. This is simply a JSON object containing any fields you need to pass to your data models:

POST /api/v1/security-token
securityContext: { 
    userId: 45, 
    orgId: "9sZSJ9LHsiYXR0cmlidXRlIjoiZ2VvaXBf",
    countries: ['us-east', 'eu-west']
}
 
Response:
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI..." }

The securityContext is just arbitrary JSON, and can be nested as deeply as you like.

How to use the Security Context

The security context is automatically available inside your data models, and is accessed like so:

cubes:
  - name: orders
    title: "Orders"
    sql: >
      SELECT * FROM public.orders
      WHERE org_id = '{ COMPILE_CONTEXT.securityContext.orgId }'
      AND user_id = { COMPILE_CONTEXT.securityContext.userId }

Or, if your customer data is, for example, split by database schema:

cubes:
  - name: orders
    title: "Orders"
    sql_table: "{ COMPILE_CONTEXT.securityContext.orgId }.orders"

If you’re unsure how best to approach data security, reach out to us—we’re here to help.

Using Jinja for Dynamic SQL

Jinja (docs (opens in a new tab)) is used under the hood to compile the models into SQL (given a particular securityContext). The { ... } notation in your model files is replaced before sending the query to your database. For example:

cubes:
  - name: customers
    title: "My customers"
    sql: >
      SELECT * 
      FROM public.customers 
      WHERE orgId = '{ COMPILE_CONTEXT.securityContext.ordId }'

with the following securityContext:

securityContext:
  orgId: abc123

compiles to:

SELECT *
FROM public.customers
WHERE ordId = 'abc123'

Conditional SQL

The nice thing about Jinja is that it also allows conditional logic for more advanced scenarios:

cubes:
  - name: customers
    title: "My customers"
    sql: >
      SELECT * 
      FROM public.customers
      {% if COMPILE_CONTEXT.securityContext.superUser %}
        WHERE 1 = 1
      {% else %}
        WHERE organisationId = '{ COMPILE_CONTEXT.securityContext.organisationId }'
      {% endif %}

For users flagged as superUser, the compiled SQL becomes:

SELECT *
FROM public.customers
WHERE 1 = 1

Multiple Values

When your security context contains an array (e.g. a list of allowed countries), use the list helper:

cubes:
  - name: customers
    title: "My customers"
    sql: >
      SELECT *
      FROM public.customers
      WHERE country IN {{ list(COMPILE_CONTEXT.securityContext.countries) }}

If countries is ["United States", "Canada", "Mexico"], the resulting SQL looks like:

SELECT *
FROM public.customers
WHERE country IN ('United States', 'Canada', 'Mexico')

Testing Security Contexts in Embeddable

To try out different security contexts in the no-code builder, define them in your repo at src/presets/security-contexts.sc.yml (here (opens in a new tab)):

- name: Nike
  securityContext:
    orgId: org5
    userId: 23478
 
- name: Adidas
  securityContext:
    orgId: 23
    userId: cmlidXRlIjoiZ2VvaXBf9sZSJ9LHsiYXR0

Each entry appears under the “View as” dropdown in the builder:

Image 0

Switch between contexts to preview your dashboard as different users.