Architecture
Access control
Complete control of your application's features and data is a key aspect of Backstack.
Overview
Access control should be thought of as "does the app version provide this feature? And if it does, what permissions does the user have to access it?"
All aspects of access control are defined using the Backstack dashboard.
Enforcing access control
The session.access
object holds all feature permissions available fot the current user. It combines multiple sources:
- Base features defined in the application's domain model and version schema
- Permission settings inherited from all roles assigned to the user
{
...
"access": {
"account-users": "crud",
...
}
}
The access key is the feature ID, and the value is a string of CRUD permissions.
Example access control function
You create a function in your preferred language to enforce access control against the session.access
values. Your function should return a boolean
result. Here's an example in JavaScript:
export function hasAccess(requiredAccess, sessionAccess) {
// Check if the requiredAccess is "*"
if (requiredAccess === "*") {
return true; // Grant access to all users
}
if (requiredAccess?.length > 0 && sessionAccess) {
// Split the control string into individual features and their permissions
const controlList = requiredAccess.split(',');
// Iterate through each feature in the control string
for (const control of controlList) {
// requiredAccess could be "*,*,*" (e.g. combining '*' constants)
if (control === "*") {
return true; // Grant access to all users
}
// Split each feature and its permissions. If no permissions assume any.
const [feature, permissions = "*"] = control.split(':');
// Check if the feature exists in the sessionAccess object
if (sessionAccess.hasOwnProperty(feature)) {
// If permissions is "*", consider it as a wildcard and return true
if (permissions === "*") {
return true;
}
// Check if the user's permissions include any of the required permissions
for (const permission of permissions) {
if (sessionAccess[feature].includes(permission)) {
// If any permission is granted, return true
return true;
}
}
}
}
}
// If none of the features have been found or none of the permissions match, return false
return false;
}
Every page should begin with an access control check. This enforces the app domain and versioning schemas.
if(!hasAccess('account-users', session?.access)){
notFound()
}
Tip
Use your routing system or middleware to run application access checks before routing to the page.
Once you enforce the application schema, you can add permission-based feature control checks.
if(hasAccess('account-users:d')){
<button>Delete User</button>
}
Feature IDs
We recommend using kebab-case for feature IDs.
Format: {subject-name}-{feature-name}
Examples:
account-users
(account subject)app-optional-features
(app subject)payment-gateway-tokens
(payment gateway subject)
The :{permissions}
suffix will be added by the API based on the user roles. Options are:
c
- Creater
- Readu
- Updated
- Delete
Then, in your hasAccess()
function, you would pass in account-users:cru
to check if the user has permission to create, read, or update the user.
Here are some examples:
// Is a page accessable?
if(!hasAccess('account-users', session?.access)){
notFound()
}
// Is the page read only? (after the above check)
const readOnly = !hasAccess('account-users:cud', session?.access)
// Is a sidebar section accessible?
if(hasAccess('account-settings,account-payment-methods,account-users,account-invoices', session?.access)) {
<AccountSidebarOptions>
// Is a sidebar option accessable?
if (hasAccess('account-payment-methods'))
<PaymentMethods/>
}
</AccountSidebarOptions>
}
// Can the user delete a record?
if(hasAccess('account-users:d', session?.access)){
<button>Delete User</button>
}
Why this convention?
Kebab-case feature IDs create clearer, more searchable code while reducing errors through visual separation. This format aligns well with REST APIs and common permission systems, making it instantly familiar to other developers.
Cross-cutting features
When defining features, developers need to balance between specific domain needs and reusable functionality. The key is to identify truly shared capabilities while keeping domain-specific features separate.
Consider these aspects when deciding between generic and specific features:
- Feature titles and descriptions are included in monetization results such as pricing and feature matrices
- Roles and permissions are assigned to every feature
- Features can be optionally enabled or disabled
- Developers must be able to easily apply them to blocks of related code
Here are some examples:
Generic features
user-settings
account-users
account-payment-settings
app-signup
image-gallaries
payment-settings
Domain-specific features
insurance-claims
lab-results
menu-items
test-builder
Define as many as you need to make access control as simple as possible.