Authorization

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.

Attribute-based access control

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:

  • The current user's role
  • Whether the workspace is personal
  • Whether the user belongs to the workspace
  • Other domain-specific attributes

A policy evaluates those attributes and returns whether an action is allowed.

Text
            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.

Policies

Every domain can define its own policy functions.

For example, the workspace domain provides policies for reading, updating, and deleting a workspace:

workspace.policy.ts
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:

Text
canReadWorkspace
canUpdateWorkspace
canDeleteWorkspace

This makes controller code easier to read and keeps permission logic out of request handlers.

Generated policies

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.

Server-side enforcement

Policies are evaluated inside controllers.

A typical protected request follows this flow:

Text
           Request
              │
              ▼
Controller loads the resource
              │
              ▼
Controller evaluates the policy
              │
              ▼
      Access is asserted
              │
              ▼
Service performs the operation

For example:

TypeScript
assertHasAccess(canUpdateWorkspace(workspace));

The shared assertion helper converts a denied policy result into a 403 Forbidden response:

assert-has-access.ts
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.

Why policies are evaluated in controllers

Controllers define the boundary between an external request and the application.

They are responsible for:

  • Resolving the authenticated user
  • Loading the relevant resource
  • Validating request input
  • Evaluating access policies
  • Calling the application service
  • Returning the response

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.

Resource-based decisions

Vuesion policies operate on resources rather than isolated role values.

Instead of spreading checks like this throughout the application:

TypeScript
if (role === 'OWNER') {
  // ...
}

Vuesion expresses the actual intent:

TypeScript
canDeleteWorkspace(workspace);

This has several advantages:

  • The rule is named after the protected action.
  • All relevant resource attributes are available.
  • Authorization logic remains in one place.
  • Policies can be tested independently.
  • Domain rules remain readable as they become more complex.

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.

Frontend authorization

The frontend may use the same resource attributes to improve the user experience.

For example, it may:

  • Hide unavailable actions
  • Disable controls
  • Avoid showing links to inaccessible resources
  • Explain why an action is unavailable

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.

Text
Frontend checks improve usability.

Server policies enforce security.

Extending a policy

As product requirements grow, policies can include additional attributes without changing how controllers use them.

For example, a future policy could consider:

  • Workspace role
  • Resource ownership
  • Subscription plan
  • Account status
  • Feature flags
  • Resource state

The controller can continue to call:

TypeScript
assertHasAccess(canUpdateWorkspace(workspace));

Only the domain policy needs to understand the expanded rule.

Why this architecture?

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:

  • A simple form of attribute-based access control
  • Explicit action-based policy names
  • Server-side enforcement
  • Independently testable rules
  • Generated authorization structure
  • Clear separation between access decisions and business logic

The result is an authorization model that starts simple but can grow with the product.

Next steps

Continue with Internationalization to learn how Vuesion handles languages, translations, locale-aware routing, and user preferences.