API Permissions
Integration of Authentication and Permissions¶
Authentication writes the current user into request.ctx.user, while permissions determine whether the user can access the ViewSet. For a complete explanation of permission classes and object-level permission wiring, see Permissions; this article only explains how they are combined to avoid repetition with the permission reference.
Basic Configuration¶
from srf.middleware.authmiddleware import set_user_to_request_ctx
from srf.permission.permission import IsAuthenticated
from srf.views import BaseViewSet
@app.middleware("request")
async def auth_middleware(request):
await set_user_to_request_ctx(request)
class ProductViewSet(BaseViewSet):
permission_classes = (IsAuthenticated,)
Before the request reaches the ViewSet, BaseViewSet.check_permissions() calls has_permission(request, view) on each class without instantiating. If it fails, it raises a Forbidden error.
If the authentication middleware detects a missing or invalid Bearer Token first, it returns a 401.
Setting Permissions by HTTP Method¶
The current framework does not have self.action or get_permissions(). When you need to distinguish operations, you should override check_permissions():
import asyncio
from sanic.exceptions import Forbidden
from srf.permission.permission import IsAuthenticated, IsRoleAdminUser
class ProductViewSet(BaseViewSet):
async def check_permissions(self, request):
if request.method.upper() in ("GET", "HEAD", "OPTIONS"):
return
classes = (
(IsAuthenticated,)
if request.method.upper() == "POST"
else (IsAuthenticated, IsRoleAdminUser)
)
for permission_class in classes:
result = permission_class.has_permission(request, self)
if asyncio.iscoroutine(result):
result = await result
if not result:
raise Forbidden(message="Forbidden")
Object-Level Permissions¶
BaseViewSet.check_object_permissions() calls has_object_permission(request, view, obj) on each class. get_object() triggers this check, so retrieve / update / destroy and custom actions that call get_object() are covered. Use @staticmethod for custom object permissions:
from srf.permission.permission import BasePermission, IsAuthenticated
class IsOwner(BasePermission):
@staticmethod
def has_object_permission(request, view=None, obj=None):
return obj.user_id == request.ctx.user.id
class OrderViewSet(BaseViewSet):
permission_classes = (IsAuthenticated, IsOwner)
List endpoints still need to filter queryset to limit visible data.
Custom Actions¶
Custom actions will also first execute the ViewSet's global check_permissions(), and cannot skip the permissions by "manually implementing the action". If a certain action requires additional permissions, you can call the permission class again within the action:
from sanic.exceptions import Forbidden
from srf.permission.permission import IsRoleAdminUser
from srf.views.decorators import action
@action(detail=True, methods=["post"], url_path="publish")
async def publish(self, request, pk):
if not IsRoleAdminUser.has_permission(request, self):
raise Forbidden(message="Requires admin privileges")
product = await self.get_object(request, pk)
# ...
Notes¶
NON_AUTH_ENDPOINTSmatches only the last part of the URL; see Authentication Middleware for details.- Custom permissions should use
@staticmethodwith signatureshas_permission(request, view=None)/has_object_permission(request, view=None, obj=None). - List visibility must still be enforced by scoping
get_queryset()(orqueryset). - For public interfaces, both authentication exemptions and ViewSet permissions need to be correctly configured.