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.
// 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 }}
After authenticating the user, you can pass in the state
values depending on the user's profile.
// 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.