Skip to Content
AuthorizationRules about the thing being accessed

Rules about the thing being accessed

@evanion/acl 0.1.0 and its patch releases up to 0.1.2, the version on npm. Values on this page were produced by running the source at 40b8f84, a later commit than the 0.1.0 tag, and are not re-executed.

Many authorization decisions depend not just on who the user is, but on the specific resource they are trying to access. For example, while any customer can view a question in the Baize shop, only the customer who asked that specific question should be allowed to edit it.

To handle this, @evanion/acl allows you to write conditions that read properties directly from the object being accessed.

Authorizing based on the resource

When a condition begins with object., the evaluator looks at the row passed to the can() call. This allows you to create dynamic rules based on the data in your database.

Instead of writing manual if checks in every handler—which inevitably drift and lead to bugs—you can declare the relationship once in your policy:

import { } from '@evanion/acl'; type = { : string; : string[] }; type = { : string; : string }; const = <, { : }>() .('question', () => .('update', .('object.askedBy', 'subject.id')), ) .(); // The same roles, so nothing on the subject tells them apart. const = { : 'sam', : ['customer'] }; const = { : 'jo', : ['customer'] }; const = { : 'urn:game:brass-birmingham', : 'sam' }; .(, 'question', 'update', ).; // -> true .(, 'question', 'update', ).; // -> false

By using p.eq('object.askedBy', 'subject.id'), you’ve created a rule that only matches if the user’s ID matches the askedBy field of the question.

The “Unevaluable” state

Unlike subject-only rules, object-based rules depend on the data you provide to the can() call. If your database query didn’t select the field that a rule needs to read, the evaluator cannot decide if the rule holds or fails.

In this case, the decision returns a fourth state: unevaluable.

The two inputs a condition can read, and the third outcome the second one adds.

If a decision is unevaluable, the missing field will list the exact paths that were missing from the object. Your application can then use this list to perform a targeted refetch of the missing data and ask the evaluator again.

`unevaluable` is not `denied`

An unevaluable result still results in allowed: false, so your app remains secure. However, it is fundamentally different from a denied result. A denied request is a permanent refusal; an unevaluable request is a temporary lack of information. Treating an unevaluable result as “forbidden” might hide features from a user who actually has permission.

Ensuring path safety

Because object.askedBy is just a string, it’s easy to make a typo (e.g., object.askedBv). In a large policy, these typos can be hard to find because the matrix still builds, and the rules simply never match.

To prevent this, @evanion/acl allows you to declare the shape of your objects in the Objects type parameter of policy<Subject, Objects>(). This enables the TypeScript compiler to check your paths against your data model at compile time.

For a deep dive into using types to secure your policy, see Typed authoring.

Next steps: Applying the rules

Now that you can write rules based on the resource, you need to apply them to your data.

  • Can this user do this? explains how to handle the unevaluable state and how to check permissions for lists of objects (canMany).
  • Which fields may they write? covers the more advanced canFields API, which decides not just if an action is allowed, but which specific fields of the object may be updated.
Last updated on