How It Works
Permio implements Resource-Specific Role-Based Access Control (RBAC), a modern approach to access management that provides fine-grained control over who can access what resources in your application.
Core Concepts
1. Resources and Resource URIs
Resources are the core building blocks of Permio's permission system. A resource represents any object, entity, or endpoint in your application that you want to protect.
Resources are identified by Resource URIs - hierarchical path strings that uniquely identify each resource:
/organisation/123
/organisation/123/project/456
/organisation/123/project/456/document/789
/user/1234
/user/1234/profile
/tenant/abc/workspace/def/file/ghi
URI Structure Best Practices
- Start with a slash: All URIs begin with
/ - Use logical hierarchy: Organize resources in a tree-like structure
- Include identifiers: Use specific IDs to identify individual resources
- Keep it readable: Use descriptive names that reflect your application structure
/project/456/documents/789
/user/1234/settings/notifications
/tenant/abc/billing/invoices/invoice-2024-001
project-456-document-789
/users/1234-settings-notifications
/random/deep/nesting/without/purpose
2. Principals
Principals are entities that can be granted permissions. In most cases, these are users, but they can also represent:
- Users: Individual people with accounts
- Service accounts: Applications or automated systems
- API keys: Programmatic access tokens
- External systems: Third-party integrations
Principals are identified by unique strings (usually user IDs or API key identifiers).
3. Permissions
Permissions define specific actions that can be performed. They represent the atomic level of access control:
{
"name": "document.read",
"description": "Allows reading documents"
}
Permission Naming Convention
Use dot notation for hierarchical permission names:
document.read- Read documentsdocument.write- Create/update documentsdocument.delete- Delete documentsuser.profile.update- Update user profilesbilling.invoice.create- Create billing invoices
4. Roles
Roles are collections of permissions that can be assigned as a group. They simplify permission management by grouping related permissions together:
{
"name": "document_editor",
"description": "Can read and write documents",
"permissions": ["document.read", "document.write"]
}
Common role patterns:
- Viewer roles: Read-only access (*.read permissions)
- Editor roles: Read and write access
- Admin roles: Full access including delete permissions
- Custom roles: Specific combinations for your use case
Hierarchical Permission Inheritance
One of Permio's most powerful features is hierarchical inheritance. Permissions granted on parent resources automatically apply to all child resources.
How Inheritance Works
/organization/123
├── /organization/123/project/456
│ ├── /organization/123/project/456/document/789
│ └── /organization/123/project/456/document/101
└── /organization/123/project/789
└── /organization/123/project/789/document/456
If a user has document.read permission on /organization/123, they automatically have read access to:
- All projects in the organization
- All documents in those projects
- Any future resources created under this organization
Inheritance Examples
Example 1: Organization-wide Access
{
"principal": "admin-user-123",
"resource_uri": "/organization/abc",
"permissions": ["organization.admin"]
}
Example 2: Project-specific Access
{
"principal": "project-manager-456",
"resource_uri": "/organization/abc/project/web-app",
"permissions": ["project.manage"]
}
Example 3: Document-specific Access
{
"principal": "editor-789",
"resource_uri": "/organization/abc/project/web-app/document/readme",
"permissions": ["document.edit"]
}
Permission Resolution Process
When checking permissions, Permio follows this resolution process:
- Direct Match: Check for permissions directly assigned to the specific resource
- Parent Traversal: Walk up the URI hierarchy checking each parent level
- Role Expansion: Expand roles into their constituent permissions
- Inheritance Application: Apply inherited permissions from parent resources
- Final Decision: Grant access if any permission grants are found
Authentication vs Authorization
Permio focuses purely on authorization (what can you do?) and is completely authentication-agnostic (who are you?).
Authentication (External)
- Identity verification: Login, passwords, MFA
- Token issuance: JWTs, session cookies, API keys
- Identity providers: Auth0, Firebase Auth, custom systems
Authorization (Permio)
- Permission checking: Can user X perform action Y on resource Z?
- Access control: Role and permission management
- Policy enforcement: Hierarchical permission inheritance
Integration Flow
1. Client ──────────→ App: Request with token
2. App ─────────────→ AuthProvider: Validate token
3. AuthProvider ────→ App: User identity
4. App ─────────────→ Permio: Check permissions
5. Permio ──────────→ App: Permission result
6. App ─────────────→ Client: Allow/block response
- Client sends request with authentication token
- Your application validates the token with your auth provider
- Auth provider confirms identity and returns user info
- Your application calls Permio to check permissions
- Permio returns permission check result
- Your application allows or denies the request
API-First Architecture
Permio is built with an API-first philosophy, offering multiple access methods:
REST API
Standard HTTP endpoints for web applications:
curl -X POST "https://api.perms.io/permissions-service/v1/permissions/check" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{"principal_id": "user_123", "resource_uris": ["/documents/doc_456"], "permissions": ["document.read"]}'
gRPC API
High-performance binary protocol for backend services:
resp, err := client.Check(ctx, &permissionsv1.CheckPermissionRequest{
PrincipalId: "user_123",
ResourceUris: []string{"/documents/doc_456"},
Permissions: []string{"document.read"},
})
Dashboard UI
Web interface for visual management: - Create and manage permissions and roles - View permission assignments - Monitor usage and analytics - Manage organizations and projects
Performance and Scalability
Performance Best Practices
1. Batch Permission Checks
{
"principal_id": "user_123",
"resource_uris": ["/doc/1", "/doc/2", "/doc/3"],
"permissions": ["read", "write"]
}
2. Use Specific Resource URIs
# Good: Specific resource
/project/456/document/789
# Avoid: Overly broad checks
/project/456/*
3. Design Efficient Hierarchies
# Good: Logical hierarchy
/tenant/abc/workspace/def/project/ghi
# Avoid: Flat structure requiring many checks
/resource-tenant-abc-workspace-def-project-ghi
Common Implementation Patterns
1. Multi-tenant Applications
Isolate tenants using hierarchical resource URIs:
# Tenant A resources
/tenant/company-a/projects/web-app
/tenant/company-a/users/user-123
# Tenant B resources
/tenant/company-b/projects/mobile-app
/tenant/company-b/users/user-456
Grant access at the tenant level:
{
"principal": "admin@company-a.com",
"resource_uri": "/tenant/company-a",
"roles": ["tenant_admin"]
}
2. Document Management System
# Workspace hierarchy
/workspace/engineering/folders/backend
/workspace/engineering/folders/backend/documents/api-spec.md
/workspace/marketing/folders/campaigns
/workspace/marketing/folders/campaigns/documents/q4-plan.pdf
Role assignments:
[
{
"principal": "john@company.com",
"resource_uri": "/workspace/engineering",
"roles": ["workspace_admin"]
},
{
"principal": "jane@company.com",
"resource_uri": "/workspace/engineering/folders/backend",
"roles": ["folder_editor"]
}
]
3. Project-based Access Control
# Project structure
/organization/acme/project/web-app
/organization/acme/project/web-app/environments/production
/organization/acme/project/web-app/environments/staging
/organization/acme/project/mobile-app
Team permissions:
[
{
"principal": "dev-team",
"resource_uri": "/organization/acme/project/web-app/environments/staging",
"permissions": ["deploy", "debug", "monitor"]
},
{
"principal": "ops-team",
"resource_uri": "/organization/acme/project/web-app/environments/production",
"permissions": ["deploy", "monitor"]
}
]
Security Considerations
Principle of Least Privilege
- Grant minimal permissions required for each role
- Regularly audit and review permission assignments
- Use time-limited permissions where appropriate
- Implement permission approval workflows for sensitive resources
Resource URI Validation
Always validate resource URIs in your application:
func validateResourceURI(uri string) error {
if !strings.HasPrefix(uri, "/") {
return errors.New("resource URI must start with /")
}
if strings.Contains(uri, "..") {
return errors.New("resource URI cannot contain path traversal")
}
// Add more validation as needed
return nil
}
Permission Boundary Enforcement
Implement checks at multiple levels:
// 1. Application level - before business logic
if !hasPermission(userID, resourceURI, "document.read") {
return errors.New("access denied")
}
// 2. API gateway level - before reaching your service
// 3. Database level - row-level security policies
Integration Examples
Express.js Middleware
function requirePermission(resource, permission) {
return async (req, res, next) => {
const userId = req.user.id;
const hasAccess = await permio.check({
principal_id: userId,
resource_uris: [resource],
permissions: [permission]
});
if (hasAccess.passed) {
next();
} else {
res.status(403).json({ error: 'Access denied' });
}
};
}
// Usage
app.get('/documents/:id',
requirePermission('/documents/' + req.params.id, 'document.read'),
getDocument
);
Django Decorator
def require_permission(resource_template, permission):
def decorator(view_func):
def wrapper(request, *args, **kwargs):
user_id = request.user.id
resource_uri = resource_template.format(**kwargs)
result = permio_client.check(
principal_id=user_id,
resource_uris=[resource_uri],
permissions=[permission]
)
if not result.passed:
return HttpResponseForbidden("Access denied")
return view_func(request, *args, **kwargs)
return wrapper
return decorator
# Usage
@require_permission('/documents/{document_id}', 'document.read')
def get_document(request, document_id):
# View logic here
pass
Go Service
type PermissionChecker struct {
client permissionsv1.PermissionsServiceClient
}
func (p *PermissionChecker) CheckAccess(ctx context.Context, userID, resourceURI, permission string) error {
resp, err := p.client.Check(ctx, &permissionsv1.CheckPermissionRequest{
PrincipalId: userID,
ResourceUris: []string{resourceURI},
Permissions: []string{permission},
})
if err != nil {
return fmt.Errorf("permission check failed: %w", err)
}
if !resp.Passed {
return fmt.Errorf("access denied to %s on %s", permission, resourceURI)
}
return nil
}
// Usage in handlers
func (h *DocumentHandler) GetDocument(w http.ResponseWriter, r *http.Request) {
userID := getUserFromContext(r.Context())
documentID := mux.Vars(r)["id"]
resourceURI := fmt.Sprintf("/documents/%s", documentID)
if err := h.permChecker.CheckAccess(r.Context(), userID, resourceURI, "document.read"); err != nil {
http.Error(w, "Access denied", http.StatusForbidden)
return
}
// Proceed with document retrieval
}
Getting Started
- Create Account: Sign up at app.perms.io
- Setup Organization: Create your organization and first project
- Generate API Key: Create an API key for your application
- Define Permissions: Create permissions that match your application's actions
- Create Roles: Group permissions into meaningful roles
- Assign Permissions: Grant roles and permissions to users on resources
- Integrate: Add permission checks to your application
- Test: Verify that access control works as expected
Ready to implement fine-grained access control? Check out our Quick Start Guide to get up and running in minutes!