Skip to main content

Command Palette

Search for a command to run...

Authorization in Django: From Permissions to Policies : Part 8 — Beyond the Permission Layer

Where Permissions End, Authorization Continues

Updated
4 min read
Authorization in Django: From Permissions to Policies : Part 8 — Beyond the Permission Layer

Up to this point, the series has been intentional.

It hasn’t tried to make Django’s authorization system more powerful.
It has tried to make its boundaries clear.

By now, permissions are no longer mysterious. They are simple records tied to models, created by convention, and checked in predictable ways. They define what actions exist and who may try them—nothing more.

That clarity brings us to a natural pause.

Django’s built-in authorization model ends exactly where it should.

What comes next matters just as much, but it is different in kind. This part is not about extending permissions. It is about understanding where they belong within a larger authorization design.

What Permissions Deliberately Refuse to Know

Earlier parts already established what permissions are not:

  • They do not encode ownership.

  • They do not understand state.

  • They do not model workflows.

  • They do not express business rules.

  • They do not change meaning over time.

This is not an omission. It is a constraint.

Django’s permission system is intentionally blind to context because context is volatile. The moment time, state, or relationships are introduced, the permission layer stops being stable. Its strings lose meaning. Its guarantees weaken. Its enforcement becomes ambiguous.

So Django draws a hard line.

Permissions answer one question only—and they answer it consistently:

May this actor attempt this class of action on this resource type?

Everything else is outside scope by design.

Authorization Does Not End Where Permissions End

Reaching this boundary does not mean authorization is complete. It means authorization must now be treated as architecture, not as a feature of a framework.

Once permissions establish the surface area of allowed actions, two additional forces inevitably appear:

  • Contextual allowance — whether an action is valid right now

  • System constraints — whether an action is valid at all

These forces cannot be collapsed into permissions without destroying their stability. They require different forms of expression, different lifecycles, and different enforcement strategies.

This is where Part 8 shifts perspective.

Three Layers, One System

At scale, authorization works only when split into three cooperating layers.

Permissions — Capability

Permissions define capability.

  • They are static, enumerable, and context-free.

  • They answer what actions exist and who may attempt them.

  • They form a contract between models, tooling, and enforcement, and are fully owned by Django.

Policies — Contextual Validity

Policies determine whether an action is valid in the current context, factoring in ownership, state, and required prior steps.

Dynamic and domain-specific, policies are evaluated at runtime and evolve with business rules. They do not grant capability; they refine it.

Invariants — System Truths

Invariants protect system integrity by defining what must never happen, regardless of actor or context.
They are unconditional, distinct from permissions and policies, and exist to prevent corruption.
They apply even when everything else says “yes.”

Enforcement as a Sequence, Not a Check

Once these layers are separated, enforcement stops being a single question and becomes a sequence of gates.

Conceptually, every protected action resolves in this order:

  1. Permission — May this actor attempt this action at all?

  2. Policy — Is the action valid in the current context?

  3. Invariant — Is the action permitted by the system’s rules of reality?

Each layer can deny independently.
Each denial has a clear meaning.
Each failure is diagnosable and auditable.

Most importantly, no layer needs to impersonate another.

Why This Architecture Scales

Systems fail when permissions are asked to explain too much.

They succeed when:

  • Permissions remain stable identifiers

  • Policies are explicit and local to the domain

  • Invariants are enforced ruthlessly and centrally

This separation allows:

  • Safe refactors

  • Predictable migrations

  • Clear audits

  • Testable authorization logic

  • Tooling that does not lie

It also explains why Django’s authorization system scales so well without being expressive. Its power comes from what it refuses to encode.

Where This Series Goes Next

This part sets the frame. What follows is not expansion, but careful construction—one layer at a time.

Part 9 steps beyond permissions and introduces policies: explicit, contextual rules that are evaluated deliberately, without leaking into the permission layer or collapsing into ad-hoc logic.

Django provides the foundation by keeping permissions small and stable. What comes next is not extension—it is architecture.

Bibliography / References

Django Foundations

Architectural Thinking

  • Martin Fowler, Patterns of Enterprise Application Architecture introduces layering and responsibility boundaries in enterprise systems.

  • Eric Evans, Domain-Driven Design establishes the separation of domain rules, policies, and infrastructure.

Authorization & Policy Models

  • OWASP Authorization Cheat Sheet outlines best practices for separating authentication, authorization, and enforcement.
    https://cheatsheetseries.owasp.org/

  • NIST SP 800-162 (ABAC Guide) formalizes policy-based authorization beyond static permissions.

API Layer Context

More from this blog

A

Abhilash PS — Engineering Thought & Software Architecture

23 posts