Concepts
Security & Multi-Tenancy

Security & Multi-tenancy

Semaphor offers multiple options for securing and segmenting customer data based on your tenant. You can use connection-level, table-level, and row/column-level security policies within Semaphor to precisely control what your users see when they log into your app.

Connection-Level Security

Connection-level policies provide strict data isolation between your tenants by connecting to dedicated databases or folders at run time. With connection-level polices, you can parameterize parts of your database strings and file paths depending on the requesting tenant. This approach eliminates dashboard duplication, and allows you to deliver the same dashboard to multiple clients in a secure manner.

Here is how it works: When a user is authenticated by your app, based on the user's tenancy and profile, you pass in the appropriate parameter along with your dashboard id and secret. Semaphor then uses the this parameter to generate the auth token that can only access the data authorized to that user.

Now you can apply this policy to your dashboard. When you authenticate the user on the server side, based on their tenacy and profile, you can consturct a connection policy object as shown below. Pass this object along with the dashboard ID and secret to the token API.

server-side.ts
// Do the server side user authentication above and resolve the tenant param
 
const tenant = 'tenant_a'; // example
 
const connectionPolicy = {
  name: 'store_sales_primary',
  params: {
    tenant: tenant,
  },
};
 
async function fetchToken() {
  try {
    const response = await fetch(TOKEN_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        dashboardId: DASHBOARD_ID,
        dashboardSecret: DASHBOARD_SECRET,
        cls: tenant ? [connectionPolicy] : [],
      }),
    });
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
 
    const token = await response.json();
    if (token?.accessToken) {
      setAuthToken(token);
      console.log('Token:', token);
    }
  } catch (error) {
    console.error('There was an error!', error);
  }
}
 
fetchToken();

Row/Column-Level Security

Row/Column-Level policies are particularly useful when all customer data resides in a single database table or a set of files that you need to logically segment based on the tenant. These policies act like row filters or WHERE clauses on your tables.

Here's how it works: When a user is authenticated by your app, depending on their tenancy and profile, you pass the appropriate parameter for that policy, along with your dashboard id and secret. Semaphor then uses the parameter to generate a token that is scoped to the data authorized for that user.

The screenshot below defines the row/column-level policy. This policy expects a state parameter in the SQL expression. Additionally, it denies the access to the discount column.

state in {{ state }}

rcls

After authenticating the user, you can pass in the state values depending on the user's profile.

server-side.ts
// Do the server side user authentication above and resolve the `state` parameter.
 
const rowPolicy = {
  name: 'state',
  params: {
    state: ['California', 'Nevada', 'Washington'],
  },
};
 
async function fetchToken() {
  try {
    const response = await fetch(TOKEN_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        dashboardId: DASHBOARD_ID,
        dashboardSecret: DASHBOARD_SECRET,
        rcls: rowPolicy,
      }),
    });
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
 
    const token = await response.json();
    if (token?.accessToken) {
      setAuthToken(token);
      console.log('Token:', token);
    }
  } catch (error) {
    console.error('There was an error!', error);
  }
}
 
fetchToken();

The policy parameter will be exapnded to SQL expresssion like below.

 
SELECT [all columns except `discount`] FROM sales_data
WHERE state in ('California', 'Nevada', 'Washington')
 

You can use connection-level policies along with row/column-level policies when your data needs both physical and logical isolation.