skip to content

After a MIDDLEWARE reshuffle, a Django project's custom audit middleware fails with 'WSGIRequest' object has no attribute 'user'; how do you diagnose and fix it?

level: seniorimportance: should knowfreq 35%

answer

  1. who sets the attribute, and when
  2. position relative to authentication
  3. early returns above it
  4. process_view runs later than you think

basics

~20 s

request.user exists only after AuthenticationMiddleware has run on the way in. The audit middleware is listed above it, AuthenticationMiddleware was removed, or a layer above it returned early. Move the audit middleware below AuthenticationMiddleware, or read the user defensively.

solid answer

~40 s

`request.user` is not built into the request: `AuthenticationMiddleware` sets it, as a lazy object, in its request phase. So I check three things. First, whether the audit middleware now sits **above** `AuthenticationMiddleware`; its before-phase then runs first and the attribute does not exist yet. Second, whether `AuthenticationMiddleware` is still in the list at all. Third, whether the failure is in the after-phase for requests that a layer above authentication answered early, such as `SecurityMiddleware`'s HTTPS redirect, `CommonMiddleware`'s `PREPEND_WWW` redirect or its `DISALLOWED_USER_AGENTS` 403; those requests never reach it. The fix is to list the audit middleware below `AuthenticationMiddleware`, read the user with `getattr(request, "user", None)` in the after-phase, or do the check in `process_view`, which runs after every before-phase. A swapped Session/Authentication pair gives a different error, `ImproperlyConfigured`, which helps tell the cases apart.

code

python · 16 lines
python
import logging

logger = logging.getLogger("audit")


class AuditMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        # Requests answered above AuthenticationMiddleware have no user attribute.
        user = getattr(request, "user", None)
        if user is not None and user.is_authenticated and request.method == "POST":
            logger.info("user=%s path=%s status=%s", user.pk, request.path, response.status_code)
        return response

go deeper

for a junior

Recall that AuthenticationMiddleware sets request.user, so middleware that reads it must be listed after it.

for a middle

Explain before-phase versus after-phase access to request.user and how an early return above authentication leaves it unset.

for a senior

Diagnose from the traceback and the list, distinguish the missing-attribute case from ImproperlyConfigured, and add a check or defensive read so the next reshuffle cannot regress.

for a principal

Decide how shared middleware declares its dependencies, for example through project checks, so ordering stays correct as teams add entries.

## Where request.user comes from A Django `HttpRequest` has no `user` attribute of its own. `AuthenticationMiddleware.process_request()` adds it on the way in, as a `SimpleLazyObject` that loads the user from the session the first time it is touched. Anything that runs **before** that point, or on a request that **never reached** it, sees no attribute, and Python raises `AttributeError: 'WSGIRequest' object has no attribute 'user'` (or `'ASGIRequest'` under ASGI). ## Diagnosis, step by step 1. **Read the traceback.** Note which middleware and which method raised: `__call__` before `get_response`, after it, or a hook. 2. **Check the position.** Find the audit middleware and `AuthenticationMiddleware` in `MIDDLEWARE`. If the audit middleware is higher, its before-phase runs first and the attribute does not exist yet. 3. **Check presence.** A reshuffle sometimes drops or comments out `AuthenticationMiddleware`; then no request ever gets `request.user`. With the admin installed, `manage.py check` reports `admin.E408` in that case. 4. **Check for early returns.** If the failure is in the after-phase and only for some requests, look for layers **above** `AuthenticationMiddleware` that answer on their own: `SecurityMiddleware`'s redirect when `SECURE_SSL_REDIRECT` is on, `CommonMiddleware`'s `PREPEND_WWW` redirect, or its 403 for `DISALLOWED_USER_AGENTS`. Those requests turn back before authentication runs, so they have no `user` when they pass the audit middleware on the way out. 5. **Rule out the other ordering bug.** If `AuthenticationMiddleware` sits above `SessionMiddleware`, the error is different: `ImproperlyConfigured`, saying the authentication middleware requires session middleware. ## Symptoms and causes | Symptom | Likely cause | Fix | |---|---|---| | Every request fails in the audit before-phase | audit middleware listed above `AuthenticationMiddleware` | move it below | | Every request fails, admin check reports `admin.E408` | `AuthenticationMiddleware` missing | restore it after `SessionMiddleware` | | Only redirects or 403s fail, in the after-phase | a layer above authentication returned early | read the user defensively, or accept those requests have no user | | `ImproperlyConfigured` about session middleware | `AuthenticationMiddleware` above `SessionMiddleware` | put `SessionMiddleware` first | ## The fixes - **Reorder.** List the audit middleware **after** `AuthenticationMiddleware`. That also places it after `SessionMiddleware`, which authentication needs. - **Be defensive in the after-phase.** `user = getattr(request, "user", None)` handles requests that were answered before authentication ran. - **Use `process_view` when you need the view too.** `process_view` hooks run inside the innermost handler, after every middleware's before-phase, so even a middleware listed above `AuthenticationMiddleware` finds `request.user` there, as long as nothing returned early. - **Enforce it.** A small project system check registered with `django.core.checks.register` can assert that the audit middleware's index is greater than `AuthenticationMiddleware`'s, so the next reshuffle fails `manage.py check` instead of production. ## A cost worth knowing `request.user` is lazy for a reason: until something touches it, no session read or user query happens. An audit middleware that reads `request.user` on **every** request forces that work even for requests that never needed a user. If the audit only matters for some paths or views, check `request.user` only there, for example inside `process_view` after deciding the view is relevant.

  • Why can a middleware listed above AuthenticationMiddleware still read request.user in process_view?
    `process_view` hooks are not part of the before-phase. Django calls them from the innermost handler, after the URL is resolved and after every middleware's `__call__` has passed the request down, including `AuthenticationMiddleware`. By then `request.user` exists, unless a layer returned early, in which case no `process_view` hook runs at all.
  • What does reading request.user in middleware cost on every request?
    `request.user` is a lazy object. Touching it loads the session to find the user's id and backend, then loads the user; with the default database session backend that is a session query plus a user query. A middleware that reads it unconditionally forces that work on requests that never needed a user, so restrict it to the paths or views that matter.

saying these in an interview costs you the question

  • request.user is always present because Django attaches it to every request.
  • Listing a middleware anywhere after SessionMiddleware is enough to read request.user.
  • A middleware above AuthenticationMiddleware cannot read request.user even in process_view.
  • The admin system checks will flag a middleware placed above AuthenticationMiddleware.
  • Reading request.user in middleware is free because it was loaded by the session middleware.