Multi-tenant SaaS in DRF: scoping every queryset to the workspace

Backend Live Coding November 13, 2026 python

I never trust ids from the client.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# ======================================================================
# Q: In a SaaS, each customer (workspace) must see only their own data. How do
# you ensure this in DRF?
#
# I never trust ids from the client. The filtering is done centrally in a mixin,
# so nobody can forget it in a new endpoint. Anything that does not belong to
# the workspace returns 404 (not 403), so its existence is not revealed.
# Watch out for nested fields too: if the serializer accepts playlist_id, the
# queryset of the PrimaryKeyRelatedField must also be filtered by workspace,
# otherwise someone can attach their screen to another customer's playlist
# (BOLA).
# ======================================================================

class WorkspaceScopedMixin:
    """Every queryset and every create is bound to the user's workspace."""
    def get_queryset(self):
        return super().get_queryset().filter(workspace=self.request.user.workspace)

    def perform_create(self, serializer):
        serializer.save(workspace=self.request.user.workspace)

class ScreenViewSet(WorkspaceScopedMixin, viewsets.ModelViewSet):
    queryset = Screen.objects.select_related('playlist')
    serializer_class = ScreenSerializer
    permission_classes = [IsAuthenticated]