Skip to Content
AuthorizationGetting started

Unreleased changes

These pages document main, which has changes 0.1.2 does not. npm install @evanion/acl gives you 0.1.2, documented here. Values on these pages are produced by running the current source on every build.

Getting started

Before you can secure your application, you need a way to define who is allowed to do what. In @evanion/acl, this is called a policy.

By the end of this page, you will have written your first policy. You’ll see how to grant the bookseller role the ability to delete questions while ensuring customers are refused, and you’ll learn how to rebuild that same logic from a JSON snapshot.

Prerequisites

To follow this guide, ensure you have the following:

  • Node 20 or newer.
  • An ESM project: Your package.json must include "type": "module", as @evanion/acl does not ship a CommonJS build.
  • TypeScript 5.5 or newer (Optional): While the package runs in plain JavaScript, TypeScript allows the compiler to check your object kinds and actions for errors.
  • A way to run TypeScript: We use npx tsx in the examples below, which handles TypeScript execution on the fly.

Note that @evanion/acl is completely self-contained. There are no API keys to manage, no external services to run, and no environment variables to configure—the policy is built directly within your own process.

Install acl

Run the command for your preferred package manager:

npm install @evanion/acl
yarn add @evanion/acl
pnpm add @evanion/acl

Create your first policy

Defining an authorization policy in @evanion/acl is a straightforward three-step chain:

  1. Name your types: Use policy<Subject, Objects>() to define the shape of the user (Subject) and the kinds of objects being protected (Objects).
  2. Define the rules: Use .for(kind, block) to write rules for a specific object kind.
  3. Build the evaluator: Call .build() to create the access object, which you will use to check permissions.

Within the .for() block, you use a helper (conventionally named p) to grant or deny access:

  • p.allow(action, ...conditions): Grants access if all provided conditions are true.
  • p.contains(path, value): A condition that checks if an array (at a given path) contains a specific value (e.g., checking if subject.roles contains 'bookseller').

Let’s put this into practice. In our example shop, Baize, only staff members with the bookseller role should be allowed to delete questions. Save the following as access.ts:

import { } from '@evanion/acl'; // Anyone signed in to Baize: a customer, or a bookseller on the shop's staff. type = { : string; : string[] }; type = { : string; : string }; // 1. Define your policy once, at module scope, when the server loads this module. const = <, { : }>() .('question', () => .('delete', .('subject.roles', 'bookseller')), ) .(); const = { : 'staff-3', : ['bookseller'] }; const = { : 'customer-41', : ['customer'] }; const = { : 'q7', : 'customer-41' }; // 2. Evaluate locally and instantly .(, 'question', 'delete', ).; // -> true .(, 'question', 'delete', ).; // -> false .(, 'question', 'delete', ).; // -> 'no-rule-matched'

To see it in action, wrap each line ending in // -> in a console.log and run npx tsx access.ts. You should see true, false, and no-rule-matched printed to your console.

The no-rule-matched result is the default deny behavior explained in the Overview—if no rule explicitly allows an action, it is refused.

Share your policy with the browser

One of the most powerful features of @evanion/acl is that the server and browser can share the exact same rules without duplicating code.

The access.matrix property contains the policy as a plain JSON document. You can send this JSON to the browser, where hydratePolicy rebuilds a fully functional evaluator.

Update the first line of access.ts to include hydratePolicy, and add these lines to the end of the file:

// The server sends the matrix as JSON text, and the browser rebuilds from it. const = .(.); const = (.()); .(, 'question', 'delete', ).; // -> true .(, 'question', 'delete', ).; // -> false

Run the file again. The results will match the server’s answers exactly. In a real application, your server would send this JSON string in a page or API response, allowing the browser to make instant, local authorization decisions without calling the server.

Note: When using hydratePolicy without type arguments, the evaluator accepts any object kind or action string, meaning the TypeScript compiler can no longer verify them.

Next steps: Refining your rules

You now have a working policy that grants access by role and can be shared across processes.

In the next section, Rules about who the user is, we’ll explore how to handle more complex scenarios—such as preventing a suspended bookseller from reading stock reports—and how to name your rules so that your application can explain exactly why a request was refused.

Last updated on