Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Django Middleware, Sessions, and Object Permissions

Last updated: 5 Oct 20268 min read
tutorial
IntermediateBy AITrove Editorial

Django constructs a request through an ordered middleware chain. On the way in, each layer receives a request before the inner view; on the way out, response work unwinds in the other direction. A middleware may also return a response early. This makes registration order part of the behavior, not a cosmetic settings choice. Session middleware must make session state available before authentication middleware resolves the user. Authentication answers who is making the request. It does not answer whether that user may inspect case 62. That second decision belongs at the object boundary and should use a server-owned rule, not a case ID supplied by the browser.

Working case

A municipal permit screen signs in reviewer 47 and shows a link to case 62. Reviewer 81 changes the URL to the same case number and receives the page because the view checks only is_authenticated. A separate logging layer records an anonymous identity because it was placed before authentication and expected request.user to be ready. Fixing the order makes request identity available, but it still does not fix the case leak. The view now queries only rows allowed for the authenticated reviewer and treats any other case as absent. A shared service applies the same scope to exports and background actions.

Implementation boundary

python
from django.contrib.auth.decorators import login_required
from django.http import Http404, JsonResponse
from .models import PermitCase


@login_required
def permit_detail(request, case_id: int):
    permit = (
        PermitCase.objects.filter(
            pk=case_id, allowed_reviewers=request.user
        )
        .values("id", "title", "status")
        .first()
    )
    if permit is None:
        raise Http404()
    return JsonResponse(permit)

Keep SessionMiddleware before AuthenticationMiddleware in settings and inspect the whole request chain when adding custom layers. Use the login requirement to reject anonymous access, then filter the requested record by both primary key and reviewer permission. Resolve only fields the response needs. In this case, allowed_reviewers is a many-to-many relation on PermitCase and its permission mapping is maintained by the server. Return 404 for both a nonexistent and an inaccessible private case if revealing existence would be harmful. A write service must repeat the permission predicate inside its own transaction; a view-only check can become stale or be bypassed by another entry point. Log a request identifier separately from private case details.

Cost and boundaries

A session lookup and permission query make a private route more expensive than a static page. The filtered lookup can be efficient when the case key and reviewer mapping have suitable indexes; inspect the generated query and actual database plan before assuming that. Fetching a model and following unrelated relations adds hidden queries, so project a small response where practical. A global middleware that queries the database for every path also taxes public pages and health checks. Keep broad identity setup in middleware and expensive object checks close to the resource. Count queries for allowed, denied, anonymous, and missing cases rather than timing only the happy path.

Failure trace

Exercise four callers: anonymous, reviewer 47 for assigned case 62, reviewer 81 for case 62, and reviewer 47 for a missing case. The latter two should obey the same disclosure rule. Remove SessionMiddleware in a test configuration and observe that authentication no longer has its prerequisite; restore the order. Then introduce a second route for an export and confirm that it reuses the object permission boundary rather than trusting a prior screen visit. Reassign the case between a read and an attempted write. The write must still check the current mapping before committing. Record the query count for the denied path.

Verification

  • Session state is prepared before authentication resolves the user.
  • An authenticated reviewer still needs permission for this case.
  • Forbidden and missing private cases obey one disclosure rule.

Practice drill

Create two reviewers and three permit cases, with one case shared only where policy allows. Write a private JSON detail route and a separate export route. First demonstrate the wrong behavior when login is treated as case permission. Then bind both routes to a common server-side scope and add tests for guessed IDs. Check settings order for session and authentication middleware. Inspect logs to ensure rejected URLs do not leak titles or submitted tokens. Finally, remove a reviewer's assignment just before a command and verify the command service rejects it under current database state.

Decision note

Make middleware establish identity, then enforce permission for the exact case at the service boundary.

Common Mistakes

  • Using login status as permission for every object.
  • Placing session-dependent middleware before its prerequisite.
  • Checking permission in one view but omitting it from another service caller.

Related lessons

Django Request and Persistence Boundaries; Django QuerySet Shape, Prefetch, and Page Cost; Django Forms, CSRF, Atomic Approval, and On-Commit Work; Django ASGI, Async Views, and the Sync ORM Boundary; Authorization and Tenant Boundaries; Federated Identity and Session Lifecycle.

Apply and check

Build Project: Django permit review service and review Web Development: Django request and persistence quiz.

web-tech
web-development
Storage details