Stateless Views & Pluggable Dashboards
In a distributed microservice architecture, your mini-apps will often need to authorize requests against external resources that they do not own.
For example, a Finance Mini-App might have a FinanceDashboardView. This dashboard aggregates data from local tables (like Invoice and Expense), but the authorization gate sits at the Organization level (e.g., "Is this user a finance admin of the active organization?").
Because the Finance Mini-App does not have an Organization table in its local PostgreSQL database, it cannot rely on standard stateful Django models for authorization.
We solve this using Stateless Resolution.
The Stateless Pattern
Using
lookup_headerin RebacViewConfig
By utilizing the lookup_header in your RebacViewConfig, the IsRebacAuthorized permission class will bypass the local database entirely. It extracts the target Object ID directly from the incoming HTTP request and queries ReBAC.
Here is a complete example of a highly decoupled, pluggable Dashboard View.
1. The Dynamic Settings
To make your mini-app truly pluggable, avoid hardcoding model imports. Define the target models in your Django settings.py so other teams can override them if they install your app.
# settings.py
FINANCE_DASHBOARD_CONFIG = {
"INVOICE_MODEL": "finance_app.Invoice",
"EXPENSE_MODEL": "finance_app.Expense",
}
2. The Dashboard View
Notice how this view does not import any local models at the top of the file, and relies entirely on the HTTP_X_CONTEXT_ORG_ID header for its ReBAC authorization check.
# views.py
from typing import ClassVar
from django.apps import apps
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from rebac.permissions import IsRebacAuthorized
from rebac.structs import RebacViewConfig
class FinanceDashboardView(APIView):
"""
A 100% Stateless and Pluggable Dashboard.
Authorizes via headers and loads models dynamically via settings.
"""
permission_classes: ClassVar[list] = [IsRebacAuthorized]
# 🛡️ THE STATELESS GUARDRAIL
# Bypasses the local DB and checks the graph directly!
#
# 1. Identity: "user:alice_123" (Auto-extracted by GatewayIdentityMiddleware)
# 2. Target: "acme_corp" (Extracted from HTTP_X_CONTEXT_ORG_ID via lookup_header)
#
# Q: "Does `user:alice_123` have the `can_view_finance_dashboard`
# permission on `organization:acme_corp`?"
rebac_config = RebacViewConfig(
object_type="organization",
read_relation="can_view_finance_dashboard",
lookup_header="HTTP_X_CONTEXT_ORG_ID"
)
def get(self, request):
# 1. Extract the authorized Org ID from the Traefik Gateway
org_id = request.META.get("HTTP_X_CONTEXT_ORG_ID")
# 2. Dynamically load the configured models into memory
dashboard_config = getattr(settings, "FINANCE_DASHBOARD_CONFIG", {})
InvoiceModel = apps.get_model(
dashboard_config.get("INVOICE_MODEL", "finance_app.Invoice")
)
ExpenseModel = apps.get_model(
dashboard_config.get("EXPENSE_MODEL", "finance_app.Expense")
)
# 3. Query the dynamically loaded models!
total_invoices = InvoiceModel.objects.filter(organization_id=org_id).count()
total_expenses = ExpenseModel.objects.filter(organization_id=org_id).count()
return Response({
"dashboard_target": org_id,
"metrics": {
"total_invoices": total_invoices,
"total_expenses": total_expenses
}
})
3. The Underlying Models (Context)
To complete the picture, here is what the models in the finance_app look like.
Notice that we DO use the RebacModelSyncMixin on these models! This perfectly demonstrates the Ownership Rule: The Finance App does not own the Organization (so the dashboard view checks it statelessly), but the Finance App does own the Invoice and Expense records, so it must sync their creation to ReBAC.
# finance_app/models.py
from django.db import models
from rebac.mixins import RebacModelSyncMixin
from rebac.structs import RebacModelConfig, RebacParentConfig, RebacCreatorConfig
class Invoice(RebacModelSyncMixin, models.Model):
# The physical link to the external Organization
organization_id = models.CharField(max_length=255, db_index=True)
amount = models.DecimalField(max_digits=10, decimal_places=2)
creator_id = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
# 🌳 Syncs the Invoice to the ReBAC Graph upon creation
rebac_config = RebacModelConfig(
object_type="invoice",
parents=[
RebacParentConfig(
relation="organization",
parent_type="organization",
local_field="organization_id"
)
],
creators=[
RebacCreatorConfig(
relation="creator",
local_field="creator_id"
)
]
)
class Expense(RebacModelSyncMixin, models.Model):
organization_id = models.CharField(max_length=255, db_index=True)
amount = models.DecimalField(max_digits=10, decimal_places=2)
creator_id = models.CharField(max_length=255)
rebac_config = RebacModelConfig(
object_type="expense",
parents=[
RebacParentConfig(
relation="organization",
parent_type="organization",
local_field="organization_id"
)
],
creators=[
RebacCreatorConfig(
relation="spender",
local_field="creator_id"
)
]
)
4. The ReBAC Schema
To make the code above function perfectly, your central ReBAC server needs a schema that understands these relationships.
Notice how the schema perfectly bridges the two concepts we just discussed:
- The View: Checks the
can_view_finance_dashboardpermission directly on theorganization. - The Models: Push tuples into the
organizationandcreator/spenderrelations on the child objects.
model
schema 1.1
type user
# ==========================================
# THE EXTERNAL CONTEXT
# ==========================================
type organization
relations
# 1. Base Roles
define member: [user]
define admin: [user] and member
define manager: [user] and member
define expert: [user]
# 2. Conditional Role or Shadow Role (The Intersection)
# This role cannot be assigned directly; it is strictly inherited
# from the admin role to ensure they are always a member.
define finance_admin: admin or manager
# 3. Permissions (The Stateless Dashboard checks this!)
define can_view_finance_dashboard: finance_admin
define can_invite_expert: finance_admin
# ==========================================
# THE OWNED DATA
# ==========================================
type invoice
relations
define organization: [organization]
define creator: [user]
# cascading permission
define can_read_invoice: creator or finance_admin from organization
type expense
relations
# 1. Structural Link (Generated by RebacParentConfig)
define parent_org: [organization]
# 2. Ownership (Generated by RebacCreatorConfig)
define spender: [user]
# 3. Permissions (Optional: cascading from the Org)
define can_read_expense: spender or finance_admin from parent_org
Architectural Benefits
- Zero Circular Imports: Models are loaded at runtime inside the
get()method. - Lean Databases: You do not need to duplicate the
Organizationtable across every single mini-app just to perform authorization checks. - Ultimate Reusability: Your mini-app can be installed as a third-party package by other teams, and they can point the dashboard to their own custom database tables!