[Django] #36837: Client.force_login won't work for permission-only backends inheriting from BaseBackend

4 views
Skip to first unread message

Django

unread,
Dec 30, 2025, 2:39:36 PM (3 days ago) 12/30/25
to django-...@googlegroups.com
#36837: Client.force_login won't work for permission-only backends inheriting from
BaseBackend
-------------------------------------+-------------------------------------
Reporter: Christian Hartung | Type:
| Cleanup/optimization
Status: new | Component:
| contrib.auth
Version: 6.0 | Severity: Normal
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
This is a follow up to #27542.

Take the `MagicAdminBackend` from the
[https://docs.djangoproject.com/en/6.0/topics/auth/customizing/#handling-
authorization-in-custom-backends documentation]. It is an Authentication
Backend that inherits from `BaseBackend` and overrides the `has_perm`
method. This leaves it with the default `get_user` method returning
`None`, making it eligible for `Client.force_login`.

Some libraries like `django-rules` and `django-guardian` work around this
by not inheriting from `BaseBackend`, and not adding a `get_user` method,
but:
* this goes against the documentation, which states that both `get_user`
and `authenticate` are required, and permission related methods are
optional
* now they don't automatically gain the `async` functionality, making it
harder for users to integrate those libraries in an `async` context

I don't know a good way around this. Some ideas are:

**Add some introspection capabilities to BaseBackend**
I was think something like:
{{{
class BaseBackend:
supports_authentication = True
supports_permissions = True

class ObjectPermissionBackend(BaseBackend):
supports_authentication = False
supports_permissions = True
}}}

This way we could change `(a)authenticate` and `force_login` to skip
backends that do not support autentication, and `(a)has_perm` to skip
backends that do not support permissions.

{{{
def _get_compatible_backends(request, **credentials):
for backend, backend_path in _get_backends(return_tuples=True):
if not getattr(backend, "supports_authentication", True):
continue

... Check signature and so on

def _user_has_perm(user, perm, obj):
for backend in auth.get_backends():
if not getattr(backend, "supports_permissions", True):
continue

if not hasattr(backend, "has_perm"):
continue

... Check permission
}}}

**Call `get_user` inside `force_login`**
Use the first backend that returns something. This was suggested on
#27542, but it could be really slow and might introduce some side effects

**Updating the documentation**
Simply update the documentation stating that:
* `get_user` required only if the backend can authenticate users
(`authenticate` returns something)
* `Client.force_login` selects the first backend that has a `get_user`.
--
Ticket URL: <https://code.djangoproject.com/ticket/36837>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
6:56 AM (9 hours ago) 6:56 AM
to django-...@googlegroups.com
#36837: Client.force_login won't work for permission-only backends inheriting from
BaseBackend
-------------------------------------+-------------------------------------
Reporter: Christian Hartung | Owner: (none)
Type: | Status: new
Cleanup/optimization |
Component: contrib.auth | Version: 6.0
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by VIZZARD-X):

* has_patch: 0 => 1

--
Ticket URL: <https://code.djangoproject.com/ticket/36837#comment:1>

Django

unread,
6:58 AM (9 hours ago) 6:58 AM
to django-...@googlegroups.com
#36837: Client.force_login won't work for permission-only backends inheriting from
BaseBackend
-------------------------------------+-------------------------------------
Reporter: Christian Hartung | Owner: (none)
Type: | Status: new
Cleanup/optimization |
Component: contrib.auth | Version: 6.0
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by VIZZARD-X):

I have opened a Pull Request with the fix and regression tests:
https://github.com/django/django/pull/20485

- The fix ensures `Client.force_login` and `AsyncClient.aforce_login`
verify that the selected backend can actually retrieve a user (via
`get_user`) before using it. This resolves the crash/incompatibility with
permission-only backends like `django-rules`.
--
Ticket URL: <https://code.djangoproject.com/ticket/36837#comment:2>
Reply all
Reply to author
Forward
0 new messages