Skip to main content

Command Palette

Search for a command to run...

Authorization in Django: From Permissions to Policies — Part 11 — A Full Workflow, End to End

Tracing a Single Request Through Authority, Validity, and State

Updated
5 min read
Authorization in Django: From Permissions to Policies — Part 11 — A Full Workflow, End to End

By now, the system is no longer abstract.

We are past definitions and isolated boundaries. Three layers are in place—permissions, policies, and invariants—each with a narrow responsibility and a distinct failure mode.

What remains is to see them operate together.

Not as a framework feature or a checklist, but as a single request moving through a real system—without blurred responsibilities, duplicated logic, or hidden assumptions.

This part follows that path end to end.

— From request handling to state mutation.
— From permission to policy to invariant enforcement.

The Scenario

Consider a familiar operation.

A user attempts to publish an article.

At a glance, this appears simple. In practice, it crosses every boundary discussed so far.

Publishing is:

  • An action not everyone may attempt

  • A transition valid only under certain conditions

  • A state change that must never partially occur

It is an ideal example not because it is exceptional, but because it is ordinary.

Step 1: Permission — May This Actor Attempt This Action?

The request enters the system.

The first question is intentionally narrow:

Is this user allowed to attempt publishing at all?

This is a permission check.

  • Not this article.

  • Not now.

  • Not under these conditions.

Only capability.

The system consults stable, declarative data:

Does the user possess the publish_article permission?

  • No article state is examined.

  • No ownership is inferred.

  • No workflow is consulted.

If this check fails, the request ends immediately.

The system has not rejected the action. It has rejected the actor.

That distinction is foundational.

Step 2: Policy — Is This Action Valid Right Now?

Once capability is established, context becomes relevant. This is where policy applies.

Policies answer a different question: Given the current state of the system, is publishing valid at this moment?

Here, the system may evaluate:

  • Is the article still a draft?

  • Has it already been published?

  • Is the user the owner or an assigned editor?

  • Are all required fields complete?

  • Is publishing allowed at this time?

— These checks are conditional.
— They are domain-specific.
— They evolve as the system evolves.

Crucially, they are evaluated before any irreversible change occurs.

When a policy fails, the meaning is precise:

  • The user is allowed to attempt this action

  • But this specific attempt conflicts with current state or rules

This is not a lack of authority.
It is a lack of validity now.

The request is denied, and the system remains unchanged.

Step 3: Approaching Mutation

At this point, two things are true:

  • The actor is permitted to attempt the action

  • The action is valid under current policy

Yet this is still not enough.

The most serious failures do not come from missing permission checks or incorrect policies. They arise during mutation:

  • Concurrent requests

  • Retries after partial failure

  • Background jobs bypassing request paths

  • Bugs that skip checks entirely

This is where the final layer becomes decisive.

Step 4: Invariants — What Must Never Be False?

Publishing is not just an action. It is a commitment.

Once published:

  • The article cannot revert to draft

  • Publication metadata must exist and agree

  • Related records must reflect the same state

  • The transition must be atomic

These are not decisions. They are guarantees.

Invariants are enforced at the point of state mutation:

  • Inside database transactions

  • Through constraints and guarded updates

  • Via explicit, irreversible transition logic

When an invariant fails, the system does not deny a request. It rejects a state.

That distinction matters.

An invariant violation means the system was about to become invalid—regardless of who initiated the change or why.

The correct response is rollback, logging, and alerting. The system protects itself.

Failure Modes, Clearly Separated

Seen together, the layers fail in fundamentally different ways:

  • Permission failure — The actor should never have been allowed to attempt this.

  • Policy failure — The request is understandable, but invalid under current conditions.

  • Invariant failure — The system was about to enter an impossible state.

Each failure tells a different story.
Each demands a different response.
None is interchangeable.

Why This Holds Under Pressure

Now consider real-world stress:

  • Two publish requests arrive simultaneously

  • A background worker retries after a timeout

  • An internal script bypasses HTTP entirely

  • A partial refactor omits a policy check

The system remains correct—not because every path is perfect, but because guarantees are enforced where mistakes cannot bypass them.

Permissions limit surface area.
Policies govern intent.
Invariants enforce reality.

This is not defensive programming. It is structural integrity.

The Architecture, Fully Assembled

At the end of the request, the system has done exactly three things:

  • Verified capability

  • Evaluated contextual validity

  • Enforced irreversible truth

Nothing leaked.
Nothing duplicated.
Nothing was silently trusted.

This is what it means for authorization to be architectural rather than procedural.

Where We Go Next (Part 12 Preview)

We have now followed a request all the way through:

  Entry → Authorization → Policy → Mutation → Enforced Reality

What remains is to examine what happens when these boundaries collapse—when permissions attempt to encode state, when policies try to guarantee truth, or when invariants are treated as optional.

That is where systems fail.

The next part examines those failure modes directly.

Not as hypotheticals, but as architectural patterns observed in real systems, under real load.

Bibliography / References

  1. Eric Evans (2003). Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley.
    https://www.domainlanguage.com/ddd/

  2. Martin Fowler (2003). Patterns of Enterprise Application Architecture. Addison-Wesley.
    https://martinfowler.com/books/eaa.html

  3. Martin Kleppmann (2017). Designing Data-Intensive Applications. O’Reilly Media.
    https://dataintensive.net/

  4. Vaughn Vernon (2013). Implementing Domain-Driven Design. Addison-Wesley.
    https://vaughnvernon.co/?page_id=168

  5. Django Software Foundation. Django Authorization Overview (Permissions and Authentication).
    https://docs.djangoproject.com/en/stable/topics/auth/

  6. Pat Helland (2015). Immutability Changes Everything. Communications of the ACM.
    https://queue.acm.org/detail.cfm?id=2884038

Thinking in Django & DRF

Part 5 of 13

Thinking in Django & DRF is a series about Django & DRF by understanding why things are designed the way they are. It explores insights from mastering Django & DRF, like syntax, shortcuts, abstraction, invariants, and architectural boundaries.

Up next

Authorization in Django: From Permissions to Policies : Part 10 — Invariants: What the System Must Never Allow

Where Rules End and Guarantees Begin

More from this blog

A

Abhilash PS — Engineering Thought & Software Architecture

23 posts