ZCore LogoZCore
How to

How to enforce Permission Scopes

Secure endpoints using automated model action scopes, custom route dependencies, and HasScopes.

ZCore eliminates authorization boilerplate by automatically generating and enforcing scope permissions per database model.

Automated Model Actions

When a model inherits from ZCore's Base, it automatically exposes the .actions() descriptor mapped to its __tablename__:

Model ActionGenerated Scope KeyMapped Route Key
Task.actions().CREATE"tasks:create"POST /
Task.actions().VIEW"tasks:view"GET /{id}
Task.actions().LISTVIEW"tasks:listview"GET / & POST /search
Task.actions().LOOKUP"tasks:lookup"POST /lookup
Task.actions().UPDATE"tasks:update"PUT /{id} & PATCH /{id}
Task.actions().DELETE"tasks:delete"DELETE /{id}

1. Automatic Scoping in BaseRouter (Zero-Config)

By simply declaring model = Task on your BaseRouter, all 8 standard endpoints are automatically protected with their corresponding HasScopes(...) rules:

# routers.py
from zcore import BaseRouter
from .models import Task
from .schemas import TaskCreate, TaskUpdate, TaskResponse
from .services import TaskService

class TaskRouter(BaseRouter[TaskCreate, TaskUpdate]):
    model = Task
    create_schema = TaskCreate
    update_schema = TaskUpdate
    schema_out = TaskResponse
    service = TaskService
    prefix = "/tasks"

# POST /tasks automatically requires "tasks:create"
# GET /tasks/{id} automatically requires "tasks:view"
# POST /tasks/lookup automatically requires "tasks:lookup"
# DELETE /tasks/{id} automatically requires "tasks:delete"
router_instance = TaskRouter()

2. Customizing Route Dependencies

To override or inject custom permissions for specific operations, override get_route_dependencies:

from typing import Any
from zcore import BaseRouter, RouteKey, HasScopes

class TaskRouter(BaseRouter[TaskCreate, TaskUpdate]):
    # ... standard configurations ...

    def get_route_dependencies(self, route_key: RouteKey, action: str) -> list[Any]:
        # Enforce an extra high-privilege scope strictly for deletion
        if route_key == RouteKey.DELETE:
            return [HasScopes("admin:all", "tasks:delete")]
            
        # Fallback to default automated model actions for other routes
        return super().get_route_dependencies(route_key, action)

3. Using HasScopes in Custom Routes

For standalone or non-CRUD FastAPI endpoints, use HasScopes directly as a dependency:

from fastapi import APIRouter, Depends
from zcore.security import HasScopes

custom_router = APIRouter()

@custom_router.get(
    "/tasks/analytics/summary",
    dependencies=[Depends(HasScopes("tasks:analytics", "reports:view"))]
)
async def get_analytics_summary():
    return {"metrics": "active"}

Superuser Bypass: By default, HasScopes(*required_scopes, allow_superuser=True) automatically allows users with is_superuser = True to bypass scope checks.

Security Responses:

  • If the user is unauthenticated or marked is_active = False $\rightarrow$ 401 Unauthorized.
  • If the user lacks any of the required scopes $\rightarrow$ 403 Forbidden.

On this page