13 KiB
13 KiB
PocketBase Security Rules
Comprehensive guide to implementing security and access control in PocketBase collections.
Table of Contents
- Understanding Security Rules
- Rule Types
- Common Patterns
- Role-Based Access Control
- Field-Level Security
- File Security
- Examples by Use Case
- Testing Rules
Understanding Security Rules
PocketBase uses four types of security rules per collection:
- listRule - Who can view the list of records
- viewRule - Who can view individual records
- createRule - Who can create new records
- updateRule - Who can update existing records
- deleteRule - Who can delete records
Rule Context Variables
@request.auth.id- ID of the authenticated user making the request@request.auth- The full authenticated user record@request.method- HTTP method (GET, POST, PATCH, DELETE)id- ID of the current record being accessed
Common Comparison Operators
=- Equals!=- Not equals<,<=,>,>=- Numeric comparisons~- Contains/in (for arrays and relations)!~- Not contains~with regex - Pattern matching (e.g.,name ~ "test")
Rule Types
Public Access (No Authentication)
// Anyone can read, only authenticated can write
listRule: ""
viewRule: ""
createRule: "@request.auth.id != ''"
Authenticated Users Only
// Only authenticated users can access
listRule: "@request.auth.id != ''"
viewRule: "@request.auth.id != ''"
createRule: "@request.auth.id != ''"
updateRule: "@request.auth.id != ''"
deleteRule: "@request.auth.id != ''"
Owner-Based Access Control
// Only the record owner can modify
createRule: "@request.auth.id != ''"
updateRule: "user_id = @request.auth.id"
deleteRule: "user_id = @request.auth.id"
Admin-Only Access
// Only admins can access (requires user role field)
listRule: "@request.auth.role = 'admin'"
viewRule: "@request.auth.role = 'admin'"
createRule: "@request.auth.role = 'admin'"
updateRule: "@request.auth.role = 'admin'"
deleteRule: "@request.auth.role = 'admin'"
Read Public, Write Owner
// Public can read, only owner can write
listRule: ""
viewRule: ""
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"
deleteRule: "author = @request.auth.id"
Common Patterns
Pattern 1: User Profile (User Can Only Modify Their Own)
listRule: "@request.auth.id = user_id"
viewRule: "@request.auth.id = user_id"
createRule: "@request.auth.id = user_id"
updateRule: "@request.auth.id = user_id"
deleteRule: "@request.auth.id = user_id"
// Where user_id is a field that stores the record owner's ID
Pattern 2: Posts/Articles (Public Read, Owner Write)
listRule: "status = 'published'"
viewRule: "status = 'published'"
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"
deleteRule: "author = @request.auth.id"
Pattern 3: Comments (Nested Under Parent)
listRule: "post.status = 'published'"
viewRule: "post.status = 'published'"
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"
deleteRule: "author = @request.auth.id"
// Assuming 'post' is a relation field
Pattern 4: Team Projects (Team Members Only)
listRule: "@request.auth.id ~ members"
viewRule: "@request.auth.id ~ members"
createRule: "@request.auth.id ~ members"
updateRule: "creator = @request.auth.id || @request.auth.id ~ members"
deleteRule: "creator = @request.auth.id"
// Where 'members' is an array of user IDs
Pattern 5: E-commerce Orders
// Customers can see their own orders
listRule: "customer = @request.auth.id"
viewRule: "customer = @request.auth.id"
createRule: "customer = @request.auth.id"
updateRule: "@request.auth.id != ''" // Only admins/staff can update
deleteRule: "@request.auth.id != ''" // Only admins can delete
Role-Based Access Control
Basic RBAC with User Roles
First, add a role field to your users collection:
{
"id": "role",
"name": "role",
"type": "select",
"required": true,
"options": {
"values": ["user", "moderator", "admin"]
}
}
Now use the role in security rules:
Regular Collection (User/Moderator/Admin)
listRule: "@request.auth.id != ''"
viewRule: "@request.auth.id != ''"
createRule: "@request.auth.id != ''"
updateRule: "user_id = @request.auth.id || @request.auth.role = 'moderator' || @request.auth.role = 'admin'"
deleteRule: "@request.auth.role = 'moderator' || @request.auth.role = 'admin'"
Admin-Only Collection
listRule: "@request.auth.role = 'admin'"
viewRule: "@request.auth.role = 'admin'"
createRule: "@request.auth.role = 'admin'"
updateRule: "@request.auth.role = 'admin'"
deleteRule: "@request.auth.role = 'admin'"
Moderator+ Collection
listRule: "@request.auth.id != ''"
viewRule: "@request.auth.id != ''"
createRule: "@request.auth.id != ''"
updateRule: "user_id = @request.auth.id || @request.auth.role != 'user'"
deleteRule: "@request.auth.role != 'user'"
Advanced RBAC: Permission Matrix
// Roles: user, author, editor, admin
// Permissions: read, write, delete, publish
// For 'posts' collection:
createRule: "@request.auth.role = 'author' || @request.auth.role = 'editor' || @request.auth.role = 'admin'"
updateRule: "author = @request.auth.id || @request.auth.role = 'editor' || @request.auth.role = 'admin'"
deleteRule: "author = @request.auth.id || @request.auth.role = 'admin'"
updateRule: "status = 'draft' && (author = @request.auth.id || @request.auth.role = 'editor' || @request.auth.role = 'admin')"
// Only editors and admins can publish
updateRule: "if(status != 'published'){ author = @request.auth.id } else { @request.auth.role = 'editor' || @request.auth.role = 'admin' }"
Field-Level Security
Restrict access to specific fields using the options parameter in the schema.
Read-Only Fields
{
"id": "created_by",
"name": "created_by",
"type": "relation",
"required": true,
"options": {
"collectionId": "users",
"cascadeDelete": false,
"maxSelect": 1
},
"presentable": false // Don't show in public APIs
}
Admin-Only Fields
{
"id": "internal_notes",
"name": "internal_notes",
"type": "text",
"options": {},
"onlyAllow": ["@request.auth.role = 'admin'"] // Only admins can set
}
User-Owned Fields
{
"id": "private_data",
"name": "private_data",
"type": "json",
"options": {},
"onlyAllow": ["user_id = @request.auth.id || @request.auth.role = 'admin'"]
}
File Security
Control who can upload, view, and delete files.
Private Files (Owner Only)
// User avatars - only owner can upload
createRule: "@request.auth.id != ''"
updateRule: "@request.auth.id = user_id"
// File access rule (in file field options):
// This controls who can access the file URL
"options": {
"maxSelect": 1,
"maxSize": 5242880,
"thumbs": ["100x100", "300x300"],
"filterSelect": "user_id = @request.auth.id"
}
Public Files (Viewable by All)
// Blog post images - authenticated users can upload
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"
// File is publicly viewable
Members-Only Files
// Team documents - only team members can access
createRule: "@request.auth.id ~ team_members"
updateRule: "@request.auth.id ~ team_members"
// File access filter
"filterSelect": "@request.auth.id ~ team_members"
Examples by Use Case
Blog Platform
// Posts
listRule: "status = 'published'"
viewRule: "status = 'published'"
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"
deleteRule: "author = @request.auth.id"
// Comments (must be authenticated)
listRule: "is_approved = true"
viewRule: "is_approved = true"
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"
deleteRule: "author = @request.auth.id"
Social Network
// Posts (public)
listRule: ""
viewRule: ""
createRule: "@request.auth.id != ''"
updateRule: "author = @request.auth.id"
deleteRule: "author = @request.auth.id"
// Private Messages
listRule: "sender = @request.auth.id || receiver = @request.auth.id"
viewRule: "sender = @request.auth.id || receiver = @request.auth.id"
createRule: "@request.auth.id != ''"
updateRule: "@request.auth.id != ''" // Only mark as read
deleteRule: "sender = @request.auth.id || receiver = @request.auth.id"
SaaS Application
// Workspaces
listRule: "@request.auth.id ~ members"
viewRule: "@request.auth.id ~ members"
createRule: "@request.auth.id ~ members"
updateRule: "@request.auth.id ~ owners"
deleteRule: "@request.auth.id ~ owners"
// Workspace Records
listRule: "workspace.members.id ?= @request.auth.id"
viewRule: "workspace.members.id ?= @request.auth.id"
createRule: "workspace.members.id ?= @request.auth.id"
updateRule: "workspace.members.id ?= @request.auth.id"
deleteRule: "workspace.owners.id ?= @request.auth.id"
E-commerce
// Products
listRule: "is_active = true"
viewRule: "is_active = true"
createRule: "@request.auth.id != ''" // Staff only in real app
updateRule: "@request.auth.id != ''" // Staff only
deleteRule: "@request.auth.id != ''" // Staff only
// Orders
listRule: "customer = @request.auth.id"
viewRule: "customer = @request.auth.id"
createRule: "customer = @request.auth.id"
updateRule: "@request.auth.id != ''" // Staff can update status
deleteRule: "@request.auth.id != ''" // Staff only
Project Management
// Projects
listRule: "@request.auth.id ~ members || @request.auth.id = owner"
viewRule: "@request.auth.id ~ members || @request.auth.id = owner"
createRule: "@request.auth.id != ''"
updateRule: "@request.auth.id = owner || @request.auth.id ~ managers"
deleteRule: "@request.auth.id = owner"
// Tasks
listRule: "project.members.id ?= @request.auth.id"
viewRule: "project.members.id ?= @request.auth.id"
createRule: "project.members.id ?= @request.auth.id"
updateRule: "assignee = @request.auth.id || @request.auth.id ~ project.managers"
deleteRule: "@request.auth.id ~ project.managers"
Testing Rules
Manual Testing
- Create a test user account
- Create records with different ownership
- Test each rule (list, view, create, update, delete)
- Test with different user roles
- Test edge cases (null values, missing relations, etc.)
Programmatic Testing
// Test with authenticated user
const pb = new PocketBase('http://127.0.0.1:8090')
// Login
await pb.collection('users').authWithPassword('test@example.com', 'password')
try {
// Try to create record
const record = await pb.collection('posts').create({
title: 'Test',
content: 'Content'
})
console.log('✓ Create successful')
} catch (e) {
console.log('✗ Create failed:', e.data)
}
try {
// Try to get list
const records = await pb.collection('posts').getList(1, 50)
console.log('✓ List successful:', records.items.length, 'records')
} catch (e) {
console.log('✗ List failed:', e.message)
}
Common Pitfalls
-
Forgetting
!= ''check// Wrong - allows anonymous users createRule: "user_id = user_id" // Correct - requires authentication createRule: "@request.auth.id != '' && user_id = @request.auth.id" -
Incorrect relation syntax
// Wrong listRule: "user.id = @request.auth.id" // Correct listRule: "user = @request.auth.id" -
Not handling null values
// If field can be null, add explicit check listRule: "status != null && status = 'published'" -
Over-restrictive rules
// This prevents admins from accessing updateRule: "author = @request.auth.id" // Better - allows admin override updateRule: "author = @request.auth.id || @request.auth.role = 'admin'"
Best Practices
- Use the principle of least privilege - Start restrictive, add permissions as needed
- Test with multiple user roles - Don't just test with admin users
- Document your rules - Add comments explaining complex rules
- Use consistent naming - Name fields clearly (e.g.,
authorinstead ofuser) - Validate on the client - Don't rely solely on server-side validation
- Use indexes - Add database indexes for fields used in rules
- Monitor access - Log security events and failed attempts
- Regular audits - Review rules periodically for security issues
Security Checklist
- All sensitive collections require authentication
- Users can only access their own data
- Admin-only collections are properly protected
- File uploads have size and type restrictions
- Delete operations are properly restricted
- Rules handle edge cases (null values, empty arrays)
- Public data is explicitly marked as public
- Internal data is never exposed via rules
- Rules are tested with multiple user types
- Complex rules are documented and reviewed