Djangocon EU: role-based access control in Django - how we forked Guardian - Gergő Simonyi

Tags: django, djangocon

(One of my summaries of the 2026 Djangocon EU in Athens).

He works for authentik (an “open-core, self-hosted identity provider”).

Note: in the talk he’ll mix “access control” and “authorization”.

In Django, every model gets some basic permissions for CRUD named after the app and the model. You can ask a user object if it has a certain permission. That method, behind the scenes, will ask all authentication backends with backend.has_perm(self, perm, obj). The default will check whether the user has the permission or if the user belongs to a group that has the permission. So that’s quite a query. The backends are queried in turn. If a backend doesn’t know if a user has a permission, it can just return None, the next backend will then be checked. If the backend knows the user has no access, it raises PermissionDenied.

The backends have methods like .has_perm(self, perm, obj), but “obj” isn’t normally called, it is None by default. But you can implement it if you want object permissions. Django-guardian is an implementation of object permissions for Django by providing an extra authentication backend: ObjectPermissionBackend.

user.has_perm("change_book") asks if the user has the permission to change all books. user.has_perm("change_book", obj=my_book) asks for a specific book.

Generic permission mechanisms deal with user, group, permission. Object permissions add userobjectpermission and groupobjectpermission. Well, that’s still reasonably OK.

But then Enterprise comes along with even more wishes:

  • Just-in-time privileged access.

  • Delegating permissions. Someone has a permission through my permissions. If I lose them, they lose them.

  • Custom permissions.

  • Group hierarchy.

  • Permission inheritance through group hierarchy.

Especially the group hierarchy was a problem. One time, an enterprise they worked with had a group that, after a couple of more groups, was a member of itself… And: django-guardian used Django’s group concept, which they couldn’t adjust.

So they started modifying django-guardian to use a custom Group model. They also made some other changes by emphasising a new Role concept and using Role to tie users/groups to permissions:

  • User can be in a group.

  • Groups can be nested.

  • A user (directly) or group can have a role.

  • A RoleObjectPermission references a role and a permission.

  • Lastly they added RoleModelPermission, replacing django’s model permission mechanism.

The core SQL query is only 52 lines, so that’s not bad. Even with 10k roles and 10k users, the query was below 1ms.

The fork is here: https://github.com/goauthentik/authentik/tree/main/packages/ak-guardian

His slides: https://github.com/gergosimonyi/djangocon-eu-2026/

https://reinout.vanrees.org/images/2026/moezel4.jpeg

Unrelated photo explanation: a trip in November to the Mosel+Eifel region in Germany. The “Oberburg” castle ruin in Manderscheid.