# PocketBase Security Rules Comprehensive guide to implementing security and access control in PocketBase collections. ## Table of Contents 1. [Understanding Security Rules](#understanding-security-rules) 2. [Rule Types](#rule-types) 3. [Common Patterns](#common-patterns) 4. [Role-Based Access Control](#role-based-access-control) 5. [Field-Level Security](#field-level-security) 6. [File Security](#file-security) 7. [Examples by Use Case](#examples-by-use-case) 8. [Testing Rules](#testing-rules) ## Understanding Security Rules PocketBase uses four types of security rules per collection: 1. **listRule** - Who can view the list of records 2. **viewRule** - Who can view individual records 3. **createRule** - Who can create new records 4. **updateRule** - Who can update existing records 5. **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) ```javascript // Anyone can read, only authenticated can write listRule: "" viewRule: "" createRule: "@request.auth.id != ''" ``` ### Authenticated Users Only ```javascript // 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 ```javascript // 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 ```javascript // 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 ```javascript // 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) ```javascript 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) ```javascript 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) ```javascript 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) ```javascript 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 ```javascript // 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: ```json { "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)** ```javascript 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** ```javascript 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** ```javascript 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 ```javascript // 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 ```json { "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 ```json { "id": "internal_notes", "name": "internal_notes", "type": "text", "options": {}, "onlyAllow": ["@request.auth.role = 'admin'"] // Only admins can set } ``` ### User-Owned Fields ```json { "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) ```javascript // 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) ```javascript // Blog post images - authenticated users can upload createRule: "@request.auth.id != ''" updateRule: "author = @request.auth.id" // File is publicly viewable ``` ### Members-Only Files ```javascript // 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 ```javascript // 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 ```javascript // 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 ```javascript // 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 ```javascript // 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 ```javascript // 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 1. Create a test user account 2. Create records with different ownership 3. Test each rule (list, view, create, update, delete) 4. Test with different user roles 5. Test edge cases (null values, missing relations, etc.) ### Programmatic Testing ```javascript // 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 1. **Forgetting `!= ''` check** ```javascript // Wrong - allows anonymous users createRule: "user_id = user_id" // Correct - requires authentication createRule: "@request.auth.id != '' && user_id = @request.auth.id" ``` 2. **Incorrect relation syntax** ```javascript // Wrong listRule: "user.id = @request.auth.id" // Correct listRule: "user = @request.auth.id" ``` 3. **Not handling null values** ```javascript // If field can be null, add explicit check listRule: "status != null && status = 'published'" ``` 4. **Over-restrictive rules** ```javascript // 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 1. **Use the principle of least privilege** - Start restrictive, add permissions as needed 2. **Test with multiple user roles** - Don't just test with admin users 3. **Document your rules** - Add comments explaining complex rules 4. **Use consistent naming** - Name fields clearly (e.g., `author` instead of `user`) 5. **Validate on the client** - Don't rely solely on server-side validation 6. **Use indexes** - Add database indexes for fields used in rules 7. **Monitor access** - Log security events and failed attempts 8. **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