django-scoped-access
Hierarchical Scoped Authorization (ABAC) + RBAC + Temporal Lifecycle + Step-Up Re-Authentication for Django & DRF.
Reference implementation of the Scoped Access Specification.
📖 Overview
django-scoped-access brings fine-grained, hierarchical multi-tenant access control to Django without polluting your business models or hardcoding organizational trees into database schemas.
Whether building a single-tenant app (flat RBAC), a multi-tenant B2B SaaS (Organization → Team), or a complex organizational system (National → Regional → District → Facility → Department), the hierarchy is pure configuration.
┌──────────────────────────────┐
│ NATIONAL │ (Root level)
└──────────────┬───────────────┘
│
┌──────────────▼──────────────┐
│ REGIONAL │
└──────────────┬───────────────┘
│
┌──────────────▼──────────────┐
│ DISTRICT │
└──────────────┬───────────────┘
│
┌──────────────▼──────────────┐
│ FACILITY │ (Can manage custom roles)
└─────────────────────────────┘
✨ Key Features
- 🌲 Hierarchy as Configuration, Not Schema: Configure any arbitrary tree depth or degrade to standard RBAC at depth 0.
- 🎯 Domain Agnostic: The engine never owns your models. Hierarchy nodes and role owners link via generic relations (
GenericForeignKey). - 🔒 Inclusive Downward Scope Coverage: A role grant at
REGIONALcovers all descendant nodes (Districts, Facilities, Units) in that subtree, and never upwards. - 🛡️ Anti-Escalation Protection (Rules R5/R7): Tenant admins can manage roles and assignments within their scope, but can never create or assign authority they do not possess themselves.
- ⏱️ Temporal Validity & Audit Trail: Assignments support
valid_from/valid_untilwindows and status transitions (ACTIVE⇄SUSPENDED→REVOKED). Assignments are never hard-deleted. - 🔐 Step-Up Re-Authentication (ReAuth): Require fresh, single-use proof of identity for high-risk actions. Ships a password verifier; PIN, TOTP or WebAuthn plug in as custom verifiers. Superusers are not exempt.
- ⚡ Database-Level Query Filtering: SQL-level filtering (
scope_filter_q()) for collection views—no in-memory Python iteration. - 🗂️ Scope-Aware Django Admin:
ScopedModelAdminfilters change lists, checks permissions per object and guards saves, bulk deletes and foreign-key choices. - 🚀 Full Django REST Framework (DRF) Integration: Drop-in permissions (
ScopedModelPermission,ScopeObjectPermission), query mixins (ScopeQuerySetMixin), write guards (ScopeWriteGuardMixin), and introspection endpoints (GET /me/access/).
⚡ Quickstart (5 Minutes)
1. Installation
# With pip
pip install "django-scoped-access[drf]"
# With uv
uv add "django-scoped-access[drf]"
2. Configuration (settings.py)
INSTALLED_APPS = [
...,
"scoped_access",
"rest_framework",
]
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"scoped_access.backends.ScopedPermissionBackend",
]
SCOPED_ACCESS = {
"HIERARCHY": [
{"level": "GLOBAL"},
{"level": "ORGANIZATION", "model": "accounts.Organization"},
{"level": "TEAM", "model": "accounts.Team", "parent": "organization"},
],
"ROLE_OWNER_LEVELS": ["ORGANIZATION"],
"GRANTABLE_PERMISSIONS": "self",
"REAUTH": {"ENABLED": True, "TTL": 300, "RATE": "5/minute"},
}
Apply database migrations:
python manage.py migrate
3. Register Resource Anchors
Declare the path from your business models to their hierarchy node:
# helpdesk/apps.py
from django.apps import AppConfig
class HelpdeskConfig(AppConfig):
name = "helpdesk"
def ready(self):
from scoped_access import register
from helpdesk.models import Ticket
# A Ticket is anchored to a Team
register(Ticket, anchor="team")
For fail-closed deployments, enable STRICT_REGISTRATION and declare intentionally global models with register_global(Model).
4. Create Roles & Grant Assignments
from scoped_access import RoleService
from scoped_access.models import ScopeAssignment
from helpdesk.models import Team
support_team = Team.objects.get(name="Support")
# 1. Create a System Role
agent_role = RoleService.create(
name="Support Agent",
permissions=[view_ticket_perm, change_ticket_perm],
by=admin_user,
)
# 2. Grant the role to Alice at the Team scope
ScopeAssignment.objects.grant(
user=alice,
role=agent_role,
scope=support_team,
by=admin_user,
)
5. Protect DRF Endpoints
# helpdesk/views.py
from scoped_access.drf import (
RequireReAuth,
ScopedModelViewSet,
)
class TicketViewSet(ScopedModelViewSet):
queryset = Ticket.objects.all()
serializer_class = TicketSerializer
def get_permissions(self):
permissions = super().get_permissions()
if self.action == "destroy":
# Deleting tickets requires Step-Up Re-Authentication
permissions.append(RequireReAuth())
return permissions
🔍 Introspection Endpoint (GET /me/access/)
Wire the standard introspection and reauth views in urls.py:
from django.urls import path
from scoped_access.drf import MeAccessView, ReAuthView
urlpatterns = [
path("api/me/access/", MeAccessView.as_view(), name="me-access"),
path("api/auth/reauth/", ReAuthView.as_view(), name="reauth"),
]
Example JSON output from GET /api/me/access/:
{
"principal": {"id": "42", "superuser": false, "active": true},
"permissions": ["helpdesk.view_ticket", "helpdesk.change_ticket"],
"assignments": [
{
"role": {"id": "7", "name": "Support Agent", "system": true},
"level": "TEAM",
"scope": {"id": "12", "label": "Customer Support — Acme Corp"},
"status": "ACTIVE",
"valid_until": null,
"permissions": ["helpdesk.view_ticket", "helpdesk.change_ticket"]
}
]
}
🧪 Testing & Conformance
Django Scoped Access is verified against a language-agnostic test suite defined in SPEC.md and conformance/:
uv run pytest
📚 Documentation
Complete documentation is available at https://django-auth-with-scoped-access.readthedocs.io/:
- Quickstart Guide
- Core Concepts & Architecture
- Configuration Reference
- Django REST Framework (DRF) Integration
- Step-Up Re-Authentication (ReAuth)
- Lifecycle & Auditability
- Security Guide & Threat Model
- Formal Specification (SPEC)
📄 License
This project is licensed under the terms of the MIT License.
Metadata
Release files for django-scoped-access 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| django_scoped_access-0.4.0.tar.gz | 146.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_scoped_access-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 192.0 kB
Release files / django_scoped_access-0.4.0.tar.gz
| Download URL | django_scoped_access-0.4.0.tar.gz |
|---|---|
| Size | 146.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5307339e68f5814658c594c8985e7cf46197d421ee72df59555ee36f8a13c086
|
|
BLAKE2b-256 checksum How to use checksums |
04146694ce4c50fb08844ece77cb335e887108dbbd31dc2f03987fb790872532
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency logRelease files / django_scoped_access-0.4.0-py3-none-any.whl
| Download URL | django_scoped_access-0.4.0-py3-none-any.whl |
|---|---|
| Size | 45.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8ffb2a6e98d1d43d6b2e88405466a537955620ee55bc26ae6c77c868eb56abb4
|
|
BLAKE2b-256 checksum How to use checksums |
09bb74b2bdd0c0b22a499de82569a93492e2e315acebf99c08698c1f94fa0509
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency logSecurity for django-scoped-access 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for django_scoped_access-0.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.