Most applications need authorization logic to control which users can access which data (resources) with which operations.
- Who: The user
- What: The resource
- How: The operation performed on the resource
One way of setting up authorization for an app is using Access Control Lists (ACLs), which are powerful since they allow us to define rules for data access in a very declarative manner. ACLs are used in Unix-like systems for filesystem permissions, for instance.
For a tutorial where you get to write a Pragma application that uses authorization rules, skip to the Example Online Course App section.
When a model is defined, many operations specific to that model are automatically exposed via the GraphQL API, which include
DELETE, and many other operations. Using access rules, you can allow or deny an incoming operation based on its kind (e.g.
READ operation), and its target resource, which is either a model field. For instance:
User is a defined model, means that
READ operations are allowed on any record of type
allow is the kind of the rule,
READ is the operation type, and
User is the resource.
The operation type of an access rule can be a single type of event (as in the above example) or you can specify multiple types of operations that the rules matches using array-like syntax. For instance:
This rule allows both
CREATE operations on the
The resource part of an access rule can refer to an entire model, or it can refer to a particular field of a model:
When the resource part of an access rule is a model, operations performed on any field of the model will match the rule. So if we have
allow READ User, then
READ operations are allowed on all fields of the
See the table of operations below to see which operations are applicable on which kinds of resources (models/fields). Remember, if an operation is applicable to both model resources (e.g.
User) and field resources (e.g.
User.username) but is applied only to a model resource (e.g.
User) then the rule will cascade to all of that model's fields.
Everything is Denied by Default
All roles are denied to perform any operation on any resource unless you define an
allow rule for them to access that exact resource, except for
READ_ON_CREATE because they are expected to work when performing a
CREATE operation and they are there to give you the option to deny them for certain roles on certain resources when you allow the
CREATE operation for that role since they have no effect unless you allow
CREATE. See the table of operations below.
Why? Well, it's a very opinionated decision we took when we decided that. When everything is denied by default, you have to think about every possible action the user can take, which can be annoying sometimes. But, on the other hand, this constraint gives us two nice upsides:
- Make the system's code explicit and clear, and the system in general easier to understand and reason about
- Make the system more secure because it forces the developer to follow the Principle of Least Privilege
This will make merely looking at the
Pragmafile good enough to understand how the system works, which is awesome!
Table of Available Operations
The following is a table specifying the available permissions, the types of resources they can be applied to, and their default rule kind (
|Permission||Operation Description||Applicable To||Default|
|Retrieve from the API||Models & model fields||deny|
|Insert a record into the database||Models||deny|
|Retrieve after creating with a ||Models & model fields||allow|
|Modify a record in the database||Models & model fields||deny|
|Add an element to an array field||Model array fields||deny|
|Remove an element from an array field||Model array fields||deny|
|Delete a record from the database||Models||deny|
|Any operation||Models & model fields||deny|
An access rule can be followed by an
if clause, specifying a condition that must be satisfied in order for the rule to match the operation. These conditions are predicates, which are functions that return a boolean value (true or false). Predicates can be imported just like any other function in Pragma, for instance:
./my-functions.js is a file containing:
The return of authorization predicates must be an object containing a
result field of type boolean. If the predicate return anything other than a boolean in the
result field, it is considered
Authorization rules are not the best way to validate data coming in, which is basically what
ageOver18 does. It is better to use the
@onWrite directive instead, but this example is implemented using an authorization rule only for demonstration purposes.
When a user model is defined, you can define a role for that specific user model. A role is a list of rules that apply only to the type of user for which the role is defined. For instance:
The rules defined within a role are only matched when an authenticated user of type
User is using the API.
Roles can only be defined for user models.
You may have noticed that access rules may be defined outside of a role block. Access rules that do not belong to any role are applied only to operations performed by anonymous (unauthenticated) users.
role, you can define rules that apply only to operations that the user is performing on their own data. For instance:
Example Online Course App
Let's say we're designing an online course management app. Each instructor teaches one course, and they control which students can enrol in that course. Instructors, students, and courses can be modeled as follows:
Here we define two user models:
Student, and one non-user model:
Now that the data models have been defined, we can start specifying the permissions we're giving to each kind of user.
Defining Access Rules
Access rules can be defined in two places: either globally, or within a
role block. A
role block specifies the permissions given to a particular user model, while global rules apply to all users (including anonymous users). For example:
Here we define a
role block where we specify that the
Instructor can do
ALL CRUD operations with
Rules with Custom Predicates
Let's say that we want to restrict
Instructors to accessing the course that belongs to them. We can do this by passing a predicate (a function that returns
false) in which we compare the course that the
Instructor is trying to access with the instructor's
Now we want to allow anonymous (unauthenticated) users to sign up as a
Student. This means that we need to allow anybody to create a new
Notice that the newly added rule is not inside any
role block. This tells Pragma that anyone can create a
Student, even if there is not user model defined for them.