Authorization in Django: From Permissions to Policies — Part 11 — A Full Workflow, End to End
Tracing a Single Request Through Authority, Validity, and State

Search for a command to run...
Tracing a Single Request Through Authority, Validity, and State

No comments yet. Be the first to comment.
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.
Responsibility Drift and the Loss of System Meaning
When we first learn Django, models are usually explained in a very concrete way. One model maps to one database table, the relationship is one-to-one, and each row represents an instance of that model. This mental model works well for most use cases ...
In a Django REST Framework application, how should responsibilities be divided? When we build a DRF APIs, we touch many layers: models for saving data, serializers for validating input, views for endpoints, and sometimes extra structure like services...
Definition Conceptual abstraction is a long-standing principle in software design—one that appears wherever systems are expected to survive change. When conceptual abstraction is discussed in the context of REST, it can sometimes feel like a REST-spe...

Definition When we design software systems, we often discuss rules, validations, and best practices. These concepts are familiar and useful, but they operate at the surface level of system behavior. Beneath all of them lies a much stronger idea—one t...
On Boundaries, Guarantees, and the Limits of Authorization

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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Eric Evans (2003). Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley.
https://www.domainlanguage.com/ddd/
Martin Fowler (2003). Patterns of Enterprise Application Architecture. Addison-Wesley.
https://martinfowler.com/books/eaa.html
Martin Kleppmann (2017). Designing Data-Intensive Applications. O’Reilly Media.
https://dataintensive.net/
Vaughn Vernon (2013). Implementing Domain-Driven Design. Addison-Wesley.
https://vaughnvernon.co/?page_id=168
Django Software Foundation. Django Authorization Overview (Permissions and Authentication).
https://docs.djangoproject.com/en/stable/topics/auth/
Pat Helland (2015). Immutability Changes Everything. Communications of the ACM.
https://queue.acm.org/detail.cfm?id=2884038