Authorization
Control who can read, write, and delete data with fine-grained permissions.
API Reference: PrismaAdapter · SyncServer
Current State (v1.0)
Gluonic v1.0 has ownership-based authorization:
import { PrismaAdapter } from '@gluonic/server-prisma'
const database = PrismaAdapter({
prisma,
models: {
post: {
ownership: 'userId', // Only owner can read/write
versioning: 'version'
}
}
})Behavior:
- ✅ Bootstrap only returns records where
userId = currentUser.id - ✅ Mutations only allowed if
userId = currentUser.id - ❌ Can’t have “public read, owner write” (see limitations below)
Limitations
Binary Permissions
Current model is all-or-nothing:
| Configuration | Read | Write | Use Case |
|---|---|---|---|
ownership: 'userId' | Owner only | Owner only | Private data |
No ownership | Everyone | Everyone | Public data |
Missing: Public read + Owner write (e.g., blog comments).
Workaround
Use client-side checks (NOT SECURE):
// Remove ownership from model
comment: { versioning: 'version' } // No ownership = public
// Client-side check (can be bypassed!)
const canEdit = comment.authorId === currentUserId
{canEdit && <EditButton />}Problem: Malicious client can bypass this.
Planned: Authorization Adapter (v0.2+)
Fine-grained permissions coming soon:
import { OwnershipAuthorization } from '@gluonic/auth-ownership'
const authz = OwnershipAuthorization({
comment: {
read: 'public', // Everyone can read
write: 'owner', // Only author can write
delete: 'owner' // Only author can delete
},
post: {
read: (userId, record) => {
// Custom logic
return record.published || record.authorId === userId
},
write: 'owner',
delete: 'owner'
}
})
const database = PrismaAdapter({
prisma,
authorization: authz // Plug in!
})Will support:
- ✅ Granular permissions (read/write/delete)
- ✅ Public read + owner write
- ✅ Custom authorization functions
- ✅ Role-based access control (RBAC)
- ✅ Server-side enforcement
Current Best Practices
Until authorization adapter is available:
1. Ownership-Based Models
For private data:
user: { ownership: 'userId', versioning: 'version' }
// Only user sees their own data ✓2. Public Models
For shared data (careful!):
tag: { versioning: 'version' }
// Everyone can read AND write ⚠️3. Server-Side Validation
Add custom validation:
import { SyncServer } from '@gluonic/server'
const server = SyncServer({
database,
auth: requireAuth,
// Validate mutations before applying
validate: async (mutation, context) => {
if (mutation.type === 'post' && mutation.op === 'd') {
// Check if user can delete this post
const post = await prisma.post.findUnique({
where: { id: mutation.id }
})
if (post.authorId !== context.user.id) {
throw new Error('Cannot delete other users posts')
}
}
}
})Note: This requires custom /tx endpoint (not using adapter’s built-in one).
4. Row-Level Security (Database)
Use database-level permissions:
-- PostgreSQL RLS
ALTER TABLE comments ENABLE ROW LEVEL SECURITY;
CREATE POLICY comments_select_policy ON comments
FOR SELECT
USING (true); -- Everyone can read
CREATE POLICY comments_modify_policy ON comments
FOR UPDATE
USING (author_id = current_setting('app.current_user_id'));Then in auth:
export async function requireAuth(req: FastifyRequest) {
// ... verify token ...
// Set session variable for RLS
await prisma.$executeRaw`
SET LOCAL app.current_user_id = ${(req as any).user.id}
`
}Future: Role-Based Access Control
Example of planned RBAC support:
import { RBACAuthorization } from '@gluonic/auth-rbac'
const authz = RBACAuthorization({
roles: {
admin: {
post: { read: true, write: true, delete: true },
user: { read: true, write: true, delete: true }
},
editor: {
post: { read: true, write: true, delete: false },
user: { read: true, write: false, delete: false }
},
viewer: {
post: { read: true, write: false, delete: false },
user: { read: true, write: false, delete: false }
}
}
})
const database = PrismaAdapter({
prisma,
authorization: authz,
getUserRole: async (userId) => {
const user = await prisma.user.findUnique({ where: { id: userId } })
return user.role // 'admin', 'editor', 'viewer'
}
})Security Checklist
✅ Required
- HTTPS in production (never HTTP)
- Validate tokens before database queries
- Use strong JWT secrets (32+ characters)
- Set token expiration
- Rate limit auth endpoints
- Log authentication failures
- Rotate secrets regularly
✅ Recommended
- Use refresh tokens for long sessions
- Implement token revocation
- Add IP-based rate limiting
- Monitor for brute force attacks
- Use secure cookies for web clients
- Implement MFA for sensitive operations
⚠️ Optional
- Row-level security (database)
- API key management
- OAuth provider integration
- Single sign-on (SSO)
Troubleshooting
401 errors on valid tokens
Problem: Client sends valid token but gets 401.
Solutions:
- Check JWT secret matches between issuer and verifier
- Verify token hasn’t expired
- Check token is sent in correct header (
Authorization: Bearer ...) - For WebSocket, check token in query param
WebSocket auth fails
Problem: WebSocket connections immediately close with 401.
Solution:
// Client must send token in query param
const ws = new WebSocket(`wss://api.example.com/sync/v1/ws?token=${token}`)Bootstrap returns empty array
Problem: User is authenticated but bootstrap returns [].
Solutions:
- Check
ownershipFieldsin adapter config matches your schema - Verify data exists with correct ownership field value
- Check user.id being used as orgId matches data
Next Steps
- Authorization Adapter - Coming in v0.2
- Row-Level Security - Database-level permissions
- API Keys - Server-to-server auth