백엔드의 권한 문자열이 UI의 예상과 일치하지 않기 때문에 아주 작은 권한 검사 라이브러리를 만들었습니다.

작성자

카테고리:

← 피드로
DEV Community · Alaa Samy · 2026-07-11 개발(SW)

Alaa Samy

A few weeks ago I was working on an internal admin dashboard (Next.js, Redux, the usual stack) and kept running into the same annoying pattern: the backend didn’t hand out permission strings like orders.read. It handed out stuff like Orders.GetAll_GET and Orders.Create_POST — literally named after the endpoint. Every module had its own slightly different set of these too.

So every “can the user do X” check in the UI ended up looking like this:

const canRead = userPermissions.includes("Orders.GetAll_GET");

Enter fullscreen mode Exit fullscreen mode

Copy that forty times across a dashboard with modules for orders, deals, users, roles, promo codes… and you’ve got forty places where a backend rename silently breaks a permission check, and nobody notices until QA does.

I looked around for something to fix this and everything I found was either React/Next.js-only or big enough that I’d have spent longer reading the docs than fixing the actual problem. I wanted something small enough to read in one sitting.

So I wrote permission-access. It’s tiny, has zero runtime dependencies, and the core doesn’t know or care that React exists.

The actual idea

The one thing I wanted to solve for real: let the UI ask a semantic question (“can they read Orders?”) without hardcoding whatever ugly endpoint name the backend picked for that module.

import { createAccessChecker, createActionMap } from "permission-access";

const actionMap = createActionMap({
  read: ["GetAll_GET", "GetList_GET"],
  create: ["Create_POST"],
});

const checker = createAccessChecker(
  { permissions: ["Orders.GetAll_GET"] },
  { actionMap },
);

checker.can("Orders", "read"); // true

Enter fullscreen mode Exit fullscreen mode

The actionMap is completely optional though. If you don’t pass one, canRead("Orders") just checks the literal string "Orders.read" — no assumptions baked in about your backend’s naming. I went back and forth on this because my first draft did ship a default REST-shaped map, and someone pointed out (rightly) that baking in an opinionated default is exactly the kind of thing that silently breaks for anyone whose backend isn’t shaped like mine. So now it’s opt-in, via a preset called REST_ACTION_MAP if you want it.

Using it without React

const checker = createAccessChecker({
  permissions: ["Orders.read", "Orders.create"],
  isAdmin: false,
});

checker.canRead("Orders");   // true
checker.canCreate("Orders"); // true
checker.canUpdate("Orders"); // false

Enter fullscreen mode Exit fullscreen mode

Using it with React

import { PermissionsProvider, usePermissions, Can } from "permission-access/react";

function App({ user }) {
  return (
    <PermissionsProvider permissions={user.permissions} isAdmin={user.isAdmin}>
      <OrdersPage />
    </PermissionsProvider>
  );
}

function OrdersPage() {
  const { canCreate } = usePermissions();

  return (
    <div>
      <Can module="Orders" action="read" fallback={<p>No access</p>}>
        <OrdersTable />
      </Can>
      {canCreate("Orders") && <CreateOrderButton />}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

That’s basically the whole API surface: createAccessChecker, has, can, a handful of canRead/canCreate/canUpdate convenience wrappers, and <PermissionsProvider> / <Can> on the React side.

What it isn’t

I want to be upfront that this is a small library. No wildcard permissions, no feature flags, no explainable “why was this denied” output — just boolean checks. It’s v0.1.x, I wrote the test suite myself, and it hasn’t been battle-tested outside my own project yet.

If you just want a small, framework-agnostic permission checker that doesn’t assume anything about your backend’s naming, it’s on npm:

npm install permission-access

Enter fullscreen mode Exit fullscreen mode

Repo’s here if you want to poke at the source or file an issue: https://github.com/alaasamy347/permission-access

Happy to hear what breaks for you if you try it.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다