Skip to content

Bound Guard in UI Layer

Problem

A UI often needs several permission checks for one signed-in user without repeating that principal in every call.

Solution

Bind the current user once with forUser() and expose the resulting action checks to the UI.

ts
import { createWard } from '@vielzeug/ward';

const ward = createWard([
  { role: 'viewer', resource: 'posts', action: 'read', effect: 'allow' },
  { role: 'editor', resource: 'posts', action: 'update', effect: 'allow' },
]);

const KNOWN_ACTIONS = ['read', 'update', 'delete'] as const;

export function usePostActions(user: { id: string; roles: string[] }) {
  const bound = ward.forUser(user);

  return {
    actions: bound.allowedActions({ resource: 'posts', knownActions: KNOWN_ACTIONS }),
    canRead: bound.explain({ resource: 'posts', action: 'read' }).allowed,
    canUpdate: bound.explain({ resource: 'posts', action: 'update' }).allowed,
    canDelete: bound.explain({ resource: 'posts', action: 'delete' }).allowed,
  };
}

Pitfalls

  • forUser() snapshots user roles; bind again when identity or roles change.
  • Keep authorization at mutation and request boundaries; hidden UI controls are not an authorization check.