Permission Management
🔧 Configuration
Before Running Examples
To run the examples on this page, you need a RESTHeart instance.
Option 1: Use RESTHeart Cloud (Recommended)
The fastest way to get started is with RESTHeart Cloud. Create a free service in minutes:
-
Sign up at cloud.restheart.com
-
Create a free API service
-
Set up your root user following the Root User Setup guide
-
Use the configuration panel above to set your service URL and credentials
Tip
|
All code examples on this page will automatically use your configured RESTHeart Cloud credentials. |
Option 2: Run RESTHeart Locally
If you prefer local development, follow the Setup Guide to install RESTHeart on your machine.
Note
|
Local instances run at http://localhost:8080 with default credentials admin:secret
|
Introduction
This section provides comprehensive information about managing permissions in RESTHeart using the ACL (Access Control List) based authorization system.
RESTHeart’s pluggable security architecture supports different authorizers. This documentation focuses on mongoAclAuthorizer and fileAclAuthorizer, which share the same permission format:
-
mongoAclAuthorizer: Stores permissions as JSON documents in a MongoDB collection (default:
/acl
) -
fileAclAuthorizer: Stores permissions in a YAML configuration file
Both authorizers provide a powerful, declarative ACL-based model for fine-grained access control.
Tip
|
For an overview of RESTHeart’s security architecture, see Security Overview and Authorization. |
Permission Format
A permission document defines who can access what resources under which conditions. The structure is the same whether stored in MongoDB (JSON) or in a configuration file (YAML).
Permission Properties
Property | Type | Description |
---|---|---|
predicate |
string |
A undertow predicate that defines when the permission applies. If the predicate resolves to |
roles |
array of strings |
The roles that this permission applies to. Use the special role |
priority |
number |
When multiple permissions match a request, higher priority permissions are evaluated first. Use this to create hierarchical permission rules. |
mongo |
object or null |
Optional. For requests to MongoDB REST API, specifies additional MongoDB-specific permissions. See MongoDB-Specific Permissions for details. |
_id |
string |
(Optional but recommended) A unique identifier for the permission. Useful for updates and debugging. |
description |
string or array |
(Optional) Human-readable description of what the permission does. |
Basic Permission Example (JSON)
{
"_id": "userCanReadInventory",
"description": "Users can read the inventory collection",
"roles": ["user"],
"predicate": "path('/inventory') and method(GET)",
"priority": 100
}
Basic Permission Example (YAML)
- _id: userCanReadInventory
description: Users can read the inventory collection
roles: [ user ]
predicate: path('/inventory') and method(GET)
priority: 100
Understanding Predicates
Predicates use the undertow predicate language to define conditions that must be met for a permission to apply.
Common Predicate Functions
Function | Description | Example |
---|---|---|
|
Matches exact path |
|
|
Matches path prefix |
|
|
Matches path with variables |
|
|
Matches HTTP method |
|
|
Compares two values |
|
|
Matches path with regex |
|
Combining Predicates
Use and
, or
, and not
to create complex conditions:
method(GET) and path-prefix('/api')
(method(GET) or method(POST)) and path('/users')
path-prefix('/admin') and not method(DELETE)
RESTHeart Custom Predicates
RESTHeart extends the undertow predicate language with additional predicates specifically designed for API security:
Query Parameter Predicates
Predicate | Description | Example |
---|---|---|
|
Request must include specified query parameters |
|
|
Request must NOT include blacklisted parameters |
|
|
Request must only include whitelisted parameters |
|
|
Request must have exactly N query parameters |
|
Request Body Predicates (BSON/JSON)
Predicate | Description | Example |
---|---|---|
|
Request body must contain specified properties |
|
|
Request body can only contain whitelisted properties |
|
|
Request body must NOT contain blacklisted properties |
|
|
Property in request must equal a value |
|
|
Array property must contain all specified values |
|
|
Array property must be subset of allowed values |
|
Note
|
When using BSON predicates with values, use valid JSON/BSON syntax: "1" for strings, 1 for numbers, {"foo":"bar"} for objects.
|
Other Custom Predicates
Predicate | Description | Example |
---|---|---|
|
Check if value is in array |
|
Using Variables in Predicates
Predicates can reference dynamic values using special variables:
Variable | Description |
---|---|
|
Properties of authenticated user (e.g., |
|
Request properties (e.g., |
|
Current MongoDB permissions object (e.g., |
|
Current date and time |
|
Value of the |
|
Path template variable (e.g., |
Example: User Can Only Access Their Own Data
{
"_id": "userCanAccessOwnProfile",
"roles": ["user"],
"predicate": "path-template('/{userid}') and equals(@user._id, ${userid})",
"priority": 100
}
This permission:
- Matches paths like /john123
, /mary456
- Extracts the {userid}
from the path
- Only authorizes if ${userid}
equals the authenticated user’s _id
MongoDB-Specific Permissions
For requests handled by RESTHeart’s MongoDB REST API, you can specify additional fine-grained permissions using the mongo
object.
MongoPermissions Structure
{
"mongo": {
"allowManagementRequests": false,
"allowBulkPatch": false,
"allowBulkDelete": false,
"allowWriteMode": false,
"readFilter": {"status": "public"},
"writeFilter": {"author": "@user._id"},
"mergeRequest": {"author": "@user._id", "createdAt": "@now"},
"projectResponse": {"password": 0, "secret": 0}
}
}
MongoPermissions Properties
Property | Description |
---|---|
|
Allows database/collection management operations (create/delete databases, collections, indexes, etc.). Default: |
|
Allows bulk PATCH operations (updating multiple documents). Default: |
|
Allows bulk DELETE operations (deleting multiple documents). Default: |
|
Allows using the |
|
Automatically adds filter conditions to all read operations |
|
Limits write operations to documents matching the filter |
|
Automatically merges properties into write request bodies |
|
Removes (or includes) specific fields from responses |
Important
|
Even if allowManagementRequests , allowBulkPatch , allowBulkDelete , or allowWriteMode is set to true , the permission’s predicate must also resolve to true for the request to be authorized.
|
readFilter
The readFilter
adds a filter condition to all read operations (GET requests) authorized by this permission. This is useful for data partitioning by role.
{
"roles": ["user"],
"predicate": "method(GET) and path-prefix('/posts')",
"mongo": {
"readFilter": {
"$or": [
{"status": "public"},
{"author": "@user._id"}
]
}
}
}
Users with the user
role can only see posts that are either public OR authored by themselves.
writeFilter
The writeFilter
limits write operations (PATCH, PUT, DELETE) to documents matching the specified condition.
{
"roles": ["user"],
"predicate": "method(PATCH) and path-prefix('/posts')",
"mongo": {
"writeFilter": {"author": "@user._id"}
}
}
Warning
|
writeFilter only restricts updates to existing documents. It cannot prevent creating documents that don’t match the filter. Use mergeRequest to enforce properties on creation.
|
mergeRequest
The mergeRequest
automatically merges specified properties into the request body. This ensures certain fields are set server-side and cannot be overridden by clients.
{
"roles": ["user"],
"predicate": "method(POST) and path('/posts')",
"mongo": {
"mergeRequest": {
"author": "@user._id",
"status": "draft",
"createdAt": "@now"
}
}
}
When a user creates a post, these properties are automatically added:
- author
is set to the user’s ID
- status
is set to "draft"
- createdAt
is set to the current timestamp
Tip
|
Use mergeRequest together with writeFilter to ensure data integrity. Set required fields on creation, then restrict updates using the filter.
|
projectResponse
The projectResponse
removes (or includes) specific fields from API responses. This is useful for hiding sensitive data.
Negative projection (hide specific fields):
{
"roles": ["user"],
"predicate": "path-prefix('/users')",
"mongo": {
"projectResponse": {"password": 0, "secret": 0, "internal.data": 0}
}
}
Positive projection (only show specific fields):
{
"roles": ["public"],
"predicate": "path-prefix('/users')",
"mongo": {
"projectResponse": {"name": 1, "email": 1, "avatar": 1}
}
}
Note
|
You can use dot notation to hide nested properties: "internal.secret": 0
|
Permission Priority
When multiple permissions match a request, RESTHeart evaluates them in order of priority (higher numbers first).
[
{
"_id": "adminFullAccess",
"roles": ["admin"],
"predicate": "path-prefix('/')",
"priority": 0,
"mongo": {
"allowManagementRequests": true,
"allowBulkPatch": true,
"allowBulkDelete": true,
"allowWriteMode": true
}
},
{
"_id": "userLimitedAccess",
"roles": ["user"],
"predicate": "path-prefix('/')",
"priority": 100,
"mongo": {
"readFilter": {"author": "@user._id"}
}
}
]
In this example:
- userLimitedAccess
(priority 100) is evaluated before adminFullAccess
(priority 0)
- If a user has both roles, the higher priority permission applies first
Tip
|
Use priority to create exception rules. For example, give high priority to specific restrictions and low priority to general access rules. |
Complete Permission Examples
Example 1: User Can Read Own Documents
{
"_id": "userCanGetOwnCollection",
"description": "Users can read their own collection with pagination",
"roles": ["user"],
"predicate": "method(GET) and path-template('/{userid}') and equals(@user._id, ${userid}) and qparams-contain(page) and qparams-blacklist(filter, sort)",
"priority": 100,
"mongo": {
"readFilter": {
"$or": [
{"status": "public"},
{"author": "@user._id"}
]
},
"projectResponse": {"log": 0}
}
}
This permission:
- Allows users to GET their own collection (e.g., /john123
)
- Requires the page
query parameter
- Blocks filter
and sort
query parameters
- Only returns documents that are public OR authored by the user
- Hides the log
field from responses
Example 2: User Can Create Documents in Own Collection
{
"_id": "userCanCreateDocumentsInOwnCollection",
"description": "Users can create documents in their collection",
"roles": ["user"],
"predicate": "method(POST) and path-template('/{userid}') and equals(@user._id, ${userid})",
"priority": 100,
"mongo": {
"mergeRequest": {
"author": "@user._id",
"status": "draft",
"createdAt": "@now"
}
}
}
This permission:
- Allows users to POST to their own collection
- Automatically sets author
, status
, and createdAt
fields
- Prevents users from setting these fields to arbitrary values
Example 3: User Can Update Own Documents
{
"_id": "userCanUpdateOwnDocuments",
"description": "Users can update their own documents",
"roles": ["user"],
"predicate": "method(PATCH) and path-template('/{userid}/*') and equals(@user._id, ${userid})",
"priority": 100,
"mongo": {
"writeFilter": {"author": "@user._id"},
"mergeRequest": {
"modifiedAt": "@now",
"modifiedBy": "@user._id"
}
}
}
This permission:
- Allows users to PATCH documents in their collection
- Only allows updating documents where author
matches the user’s ID
- Automatically sets modifiedAt
and modifiedBy
on updates
Example 4: Admin Full Access
{
"_id": "adminCanDoEverything",
"description": ["Admin role can do everything"],
"roles": ["admin"],
"predicate": "path-prefix('/')",
"priority": 0,
"mongo": {
"allowManagementRequests": true,
"allowBulkPatch": true,
"allowBulkDelete": true,
"allowWriteMode": true
}
}
Example 5: Public Read-Only Access
{
"_id": "publicCanReadProducts",
"description": "Unauthenticated users can read public products",
"roles": ["$unauthenticated"],
"predicate": "method(GET) and path('/products')",
"priority": 100,
"mongo": {
"readFilter": {"published": true, "visibility": "public"},
"projectResponse": {"internalNotes": 0, "cost": 0}
}
}
Example 6: JWT-based Multi-tenant Access
- _id: jwtTenantAccess
roles: [ jwt-user ]
predicate: >
path-template('/{tenant}/data')
and in(value=${tenant}, array=@user.tenants)
priority: 100
mongo:
readFilter: >
{"tenantId": "${tenant}"}
mergeRequest: >
{"tenantId": "${tenant}", "userId": "@user.sub"}
This permission (YAML format):
- Extracts {tenant}
from path like /acme/data
- Checks if user’s JWT contains tenant
in their tenants
array
- Filters data by tenantId
- Automatically adds tenantId
and userId
to write requests
Managing Permissions
With mongoAclAuthorizer (MongoDB Storage)
List All Permissions
cURL
curl -i -X GET [RESTHEART-URL]/acl \
-H "Authorization: Basic [BASIC-AUTH]"
HTTPie
http GET [RESTHEART-URL]/acl \
Authorization:"Basic [BASIC-AUTH]"
JavaScript
const credentials = btoa(`${username}:${password}`);
fetch('[RESTHEART-URL]/acl', {
method: 'GET',
headers: { 'Authorization': `Basic ${credentials}` }
})
.then(response => response.json())
.then(data => console.log('Permissions:', data));
Create a Permission
cURL
curl -i -X POST [RESTHEART-URL]/acl \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{
"_id": "userCanReadInventory",
"roles": ["user"],
"predicate": "method(GET) and path-prefix(\"/inventory\")",
"priority": 100
}'
HTTPie
http POST [RESTHEART-URL]/acl \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json \
_id="userCanReadInventory" \
roles:='["user"]' \
predicate="method(GET) and path-prefix(\"/inventory\")" \
priority:=100
JavaScript
const credentials = btoa(`${username}:${password}`);
fetch('[RESTHEART-URL]/acl', {
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
"_id": "userCanReadInventory",
"roles": ["user"],
"predicate": "method(GET) and path-prefix(\"/inventory\")",
"priority": 100
})
})
.then(response => {
if (response.ok) console.log('Permission created');
});
Update a Permission
cURL
curl -i -X PATCH [RESTHEART-URL]/acl/userCanReadInventory \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{
"priority": 200
}'
HTTPie
http PATCH [RESTHEART-URL]/acl/userCanReadInventory \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json \
priority:=200
Delete a Permission
cURL
curl -i -X DELETE [RESTHEART-URL]/acl/userCanReadInventory \
-H "Authorization: Basic [BASIC-AUTH]"
HTTPie
http DELETE [RESTHEART-URL]/acl/userCanReadInventory \
Authorization:"Basic [BASIC-AUTH]"
With fileAclAuthorizer (Configuration File)
Edit your ACL configuration file (e.g., acl.yml
):
fileAclAuthorizer:
conf-file: ./acl.yml
permissions:
- _id: adminFullAccess
roles: [ admin ]
predicate: path-prefix('/')
priority: 0
mongo:
allowManagementRequests: true
allowBulkPatch: true
allowBulkDelete: true
- _id: userCanReadInventory
roles: [ user ]
predicate: method(GET) and path-prefix('/inventory')
priority: 100
- _id: userCanCreateOwnData
roles: [ user ]
predicate: >
method(POST)
and path-template('/{userid}')
and equals(@user._id, ${userid})
priority: 100
mongo:
mergeRequest: >
{"author": "@user._id", "createdAt": "@now"}
After editing the file, restart RESTHeart to apply changes.
Configuration
mongoAclAuthorizer Configuration
mongoAclAuthorizer:
acl-db: restheart
acl-collection: acl
# Users with root-role bypass all ACL checks
root-role: admin
cache-enabled: true
cache-size: 1000
cache-ttl: 5000
cache-expire-policy: AFTER_WRITE
fileAclAuthorizer Configuration
fileAclAuthorizer:
# Path to ACL configuration file (absolute or relative to restheart config)
conf-file: ./acl.yml
# Or define permissions inline:
permissions:
- role: admin
predicate: path-prefix('/')
priority: 0
Best Practices
1. Use Descriptive IDs and Descriptions
{
"_id": "userCanReadOwnPosts",
"description": "Users with 'user' role can read posts they authored or public posts",
...
}
2. Leverage Priority for Exception Handling
Use high priority for specific rules, low priority for general rules:
[
{
"_id": "blockSensitiveEndpoint",
"roles": ["user"],
"predicate": "path('/admin/secrets')",
"priority": 1000,
"mongo": null
},
{
"_id": "allowGeneralAccess",
"roles": ["user"],
"predicate": "path-prefix('/admin')",
"priority": 100
}
]
3. Combine writeFilter and mergeRequest
Always use both together to ensure data integrity:
{
"mongo": {
"writeFilter": {"author": "@user._id"},
"mergeRequest": {"author": "@user._id"}
}
}
4. Test Permissions Thoroughly
Start with restrictive permissions and gradually add more access. Test with different user roles and edge cases.
5. Use projectResponse to Hide Sensitive Data
{
"mongo": {
"projectResponse": {
"password": 0,
"ssn": 0,
"creditCard": 0
}
}
}
6. Whitelist Instead of Blacklist When Possible
Prefer bson-request-whitelist
and qparams-whitelist
over blacklists for better security:
bson-request-whitelist(name, email, age) // Only these fields allowed
7. Use Path Templates for User-Scoped Resources
path-template('/{userid}/documents') and equals(@user._id, ${userid})
8. Document Your Permission Strategy
Maintain a clear permission hierarchy and document the roles and their intended access levels.
Common Patterns
Pattern 1: User-Owned Resources
{
"_id": "userCRUDOwnResources",
"roles": ["user"],
"predicate": "path-template('/{userid}/*') and equals(@user._id, ${userid})",
"priority": 100,
"mongo": {
"writeFilter": {"author": "@user._id"},
"readFilter": {"author": "@user._id"},
"mergeRequest": {"author": "@user._id"}
}
}
Pattern 2: Public Read, Authenticated Write
[
{
"_id": "publicCanRead",
"roles": ["$unauthenticated"],
"predicate": "method(GET) and path-prefix('/posts')",
"priority": 100,
"mongo": {
"readFilter": {"status": "published"}
}
},
{
"_id": "userCanWrite",
"roles": ["user"],
"predicate": "(method(POST) or method(PATCH)) and path-prefix('/posts')",
"priority": 100,
"mongo": {
"mergeRequest": {"author": "@user._id"}
}
}
]
Pattern 3: Multi-Tenant with JWT
- _id: tenantDataAccess
roles: [ tenant-user ]
predicate: >
path-template('/{tenantId}/data')
and in(value=${tenantId}, array=@user.tenants)
priority: 100
mongo:
readFilter: >
{"tenantId": "${tenantId}"}
writeFilter: >
{"tenantId": "${tenantId}"}
mergeRequest: >
{"tenantId": "${tenantId}"}
Pattern 4: Time-Based Access
{
"_id": "timeLimitedAccess",
"roles": ["premium"],
"predicate": "path-prefix('/premium-content')",
"priority": 100,
"mongo": {
"readFilter": {
"$or": [
{"expiresAt": {"$gt": "@now"}},
{"expiresAt": {"$exists": false}}
]
}
}
}
Troubleshooting
Permission Not Applied
-
Check if the
predicate
matches your request -
Verify the user has the specified
roles
-
Check if a higher priority permission is overriding it
-
Ensure the collection/file is correctly configured in the authorizer settings
writeFilter Not Preventing Creation
writeFilter
only restricts updates to existing documents. Use mergeRequest
to enforce fields on creation and bson-request-whitelist
to limit allowed fields.
Variables Not Resolving
Ensure you’re using the correct syntax:
- @user._id
for user properties
- ${variable}
for path template variables
- Quotes around strings: value='"draft"'
Additional Resources
Tip
|
Watch Authorization via file and MongoDB for a video tutorial on permissions. |