In an async Django view, why should you use await request.auser() and alogin() instead of request.user and login()?
answer
- a lazy object hides a query
- sync database code in an event loop
- a-prefixed twins since 5.0
- cached per request
basics
~20 srequest.user is a lazy object whose first access reads the session and queries the user synchronously, which Django forbids inside an event loop. request.auser(), alogin(), alogout() and aauthenticate(), added in Django 5.0, do the same work asynchronously.
solid answer
~30 s`AuthenticationMiddleware` sets `request.user` to a `SimpleLazyObject` around `get_user()`. Evaluating it reads the session and runs the backend's `get_user()`, both synchronous database calls, so doing it in an `async def` view raises `SynchronousOnlyOperation` ('You cannot call this from an async context'). Django 5.0 added coroutine versions: `await request.auser()` resolves the user through `aget_user()` and caches it on the request; `aauthenticate()`, `alogin()`, `alogout()` and `aupdate_session_auth_hash()` mirror their sync twins with the async session API. They keep the same rules, including the session-key rotation in `alogin()` and flush in `alogout()`. `auser()` returns an `AnonymousUser` when nobody is signed in, just like `request.user`.
code
python · 15 linesfrom django.http import JsonResponse
from gym.models import Booking
async def next_class(request):
user = await request.auser()
if not user.is_authenticated:
return JsonResponse({'detail': 'Sign in required.'}, status=401)
booking = await (
Booking.objects.filter(member=user).order_by('starts_at').select_related('gym_class').afirst()
)
if booking is None:
return JsonResponse({'next': None})
return JsonResponse({'next': booking.gym_class.title, 'starts_at': booking.starts_at.isoformat()})go deeper
Recall that async views read the user with await request.auser() and sign in with alogin(), and that both exist since Django 5.0.
Explain why request.user fails in an async view: a lazy object that runs synchronous session and ORM queries, which Django blocks with SynchronousOnlyOperation.
Check custom backends and middleware for sync-only paths, and keep request.user and request.auser() from diverging within one request.
Adopt async auth only where the view is truly async end to end; a sync view under ASGI is often simpler than converting the whole sign-in path.
## The problem with request.user in async code `AuthenticationMiddleware` does not load the user eagerly. It sets: - `request.user = SimpleLazyObject(lambda: get_user(request))` - `request.auser = partial(auser, request)` The lazy object runs `get_user()` the first time an attribute is read. `get_user()` reads the session (a database query with the default session engine) and calls the backend's `get_user()`, which with `ModelBackend` is an ORM `get()`. Django marks database operations as **async-unsafe**: if they run in a thread with a running event loop they raise `SynchronousOnlyOperation` with the message "You cannot call this from an async context - use a thread or sync_to_async." So in an `async def` view, `if request.user.is_authenticated:` fails as soon as it needs data it has not loaded yet. ## The async twins added in Django 5.0 | Sync | Async | Notes | |---|---|---| | `request.user` | `await request.auser()` | resolves through `aget_user()`; result cached as `request._acached_user` | | `authenticate()` | `aauthenticate()` | calls each backend's `aauthenticate()` | | `login()` | `alogin()` | `acycle_key()` / `aflush()` and `aset()` on the session | | `logout()` | `alogout()` | `user_logged_out.asend()`, then `aflush()` | | `update_session_auth_hash()` | `aupdate_session_auth_hash()` | `acycle_key()` then stores the new hash | | `get_user()` | `aget_user()` | same hash verification, async session reads | The behaviour is identical: `alogin()` still rotates the key for an anonymous session and flushes one that belonged to someone else, and `aget_user()` still flushes a session whose password hash no longer matches. ## A worked example A gym portal's async endpoint that returns a member's next booked class: 1. `user = await request.auser()` - safe in the event loop, and `AnonymousUser` if nobody is signed in. 2. Check `user.is_authenticated` - a plain attribute, no I/O. 3. Query bookings with the async ORM methods such as `afirst()` or `async for`. Backends matter here: `BaseBackend.aauthenticate()` and `aget_user()` default to wrapping the sync methods in `sync_to_async`, so a custom backend works in async views even if it never defines async methods, while `ModelBackend` provides native async lookups. ## What the async session calls look like Inside `alogin()` Django uses the session's async methods rather than dictionary access: 1. `await request.session.ahas_key('_auth_user_id')` to decide between rotation and flush; 2. `await request.session.acycle_key()` or `await request.session.aflush()`; 3. three `await request.session.aset(...)` calls for the user id, backend path and hash; 4. `await user_logged_in.asend(...)` so async receivers run without blocking. Your own async views should follow the same pattern when they read or write session data. ## Where the sync API is still fine - In ordinary `def` views, including under an ASGI server; Django runs sync views in a thread, so `request.user` and `login()` work normally. - In templates rendered from sync views. - In async views, only if you wrap the sync call yourself with `sync_to_async`, which the async twins make unnecessary. ## Gotchas - **Mixing the two in one request.** `request.user` and `request.auser()` cache their results separately. `alogin()` and `alogout()` reset both through the same internal helper, but it is still clearer to pick one accessor per code path. - **Decorators.** Since Django 5.1, `login_required`, `permission_required` and `user_passes_test` detect coroutine views and load the user with `await request.auser()`, so they can wrap an async view directly. - **Testing.** `AsyncClient` and the test client's `alogin()`/`alogout()` (also 5.0) let tests exercise these paths without a real sign-in form.
- Does a custom authentication backend need async methods to work with aauthenticate()?No. `BaseBackend.aauthenticate()` and `aget_user()` default to running the sync `authenticate()` and `get_user()` through `sync_to_async`. Writing native async versions only avoids the thread hop; the sync implementation keeps working in async views.
saying these in an interview costs you the question
- request.user is loaded before the view runs, so it is safe in async views
- alogin() skips the session key rotation that login() performs
- request.auser() returns None when nobody is signed in
- Async views cannot log users in without sync_to_async in Django 6.1