Authentication identifies the user.
Authorization determines what that user is allowed to do.
Vuesion treats these as two separate concerns. A valid session proves who is making a request, but every protected operation must still verify whether that user may access or modify the requested resource.
Vuesion uses a lightweight form of attribute-based access control, commonly referred to as ABAC.
Instead of checking only a global role, access decisions are based on attributes of the resource being accessed.
For example, a workspace resource may already include information such as:
A policy evaluates those attributes and returns whether an action is allowed.
User session
│
▼
Loaded resource with access attributes
│
▼
Policy
│
▼
Allowed or forbidden
This keeps authorization close to the domain and makes access decisions easy to understand.
Every domain can define its own policy functions.
For example, the workspace domain provides policies for reading, updating, and deleting a workspace:
import type { WorkspaceDetailView } from '#shared/types/domain/Workspace';
export const canReadWorkspace = (resource: WorkspaceDetailView) => {
return !!resource.currentUserRole;
};
export const canUpdateWorkspace = (resource: WorkspaceDetailView) => {
return resource.currentUserRole === 'OWNER' || resource.currentUserRole === 'ADMIN';
};
export const canDeleteWorkspace = (resource: WorkspaceDetailView) => {
return resource.currentUserRole === 'OWNER' && resource.isPersonal === false;
};
The policy names describe actions rather than implementation details:
canReadWorkspace
canUpdateWorkspace
canDeleteWorkspace
This makes controller code easier to read and keeps permission logic out of request handlers.
Vuesion generates the initial policy files for each domain.
You do not need to create the surrounding authorization structure manually whenever a new domain is added.
The generated policies provide a clear place for defining the resource-specific rules of that domain. You can then extend or replace those rules as the product requirements evolve.
This keeps authorization consistent across the application while avoiding repetitive setup work.
Policies are evaluated inside controllers.
A typical protected request follows this flow:
Request
│
▼
Controller loads the resource
│
▼
Controller evaluates the policy
│
▼
Access is asserted
│
▼
Service performs the operation
For example:
assertHasAccess(canUpdateWorkspace(workspace));
The shared assertion helper converts a denied policy result into a 403 Forbidden response:
export const assertHasAccess = (value: boolean) => {
if (value === false) {
throw ForbiddenError();
}
};
This keeps policies simple. They only answer whether an action is allowed, while the assertion helper handles the HTTP error.
Controllers define the boundary between an external request and the application.
They are responsible for:
Authorization is therefore enforced before the service performs the requested operation.
Services can stay focused on business behavior instead of interpreting HTTP requests or user interface state.
Vuesion policies operate on resources rather than isolated role values.
Instead of spreading checks like this throughout the application:
if (role === 'OWNER') {
// ...
}
Vuesion expresses the actual intent:
canDeleteWorkspace(workspace);
This has several advantages:
A role may be one input into a policy, but it is not necessarily the only one.
For example, deleting a workspace depends on both the user's role and whether the workspace is personal.
The frontend may use the same resource attributes to improve the user experience.
For example, it may:
These checks are not a security boundary.
A user can manipulate client-side state or send requests directly. Every protected operation must therefore evaluate its policy again on the server.
Frontend checks improve usability.
Server policies enforce security.
As product requirements grow, policies can include additional attributes without changing how controllers use them.
For example, a future policy could consider:
The controller can continue to call:
assertHasAccess(canUpdateWorkspace(workspace));
Only the domain policy needs to understand the expanded rule.
Authorization rules tend to become scattered when they are implemented directly inside controllers, services, and components.
Vuesion avoids this by giving every domain a dedicated policy layer.
This approach provides:
The result is an authorization model that starts simple but can grow with the product.
Continue with Internationalization to learn how Vuesion handles languages, translations, locale-aware routing, and user preferences.