← All posts
·7 min read
#django#database#tutorial

Django Auth Tables and Permissions Explained

Every table Django creates for authentication: auth_user, auth_group, auth_permission, what each one does and how the permission system works.

The short version

Django's auth system is 9 tables: auth_user, auth_group, auth_permission, django_content_type, django_admin_log, and three join tables (auth_user_groups, auth_user_user_permissions, auth_group_permissions). The permission system is built on content types, each model gets a default add, change, delete, and view permission. See the full interactive version in the Django auth schema diagram.

The core tables

auth_user

ColumnTypeWhat it means
idint (PK)Auto-incrementing primary key.
passwordvarchar(128)Hashed password (PBKDF2 by default).
last_logindatetimeWhen the user last logged in.
is_superuserboolBypasses all permission checks.
usernamevarchar(150)Unique username.
emailvarchar(254)Email address.
is_staffboolCan access the Django admin.
is_activeboolSet to False instead of deleting users.

auth_group

ColumnTypeWhat it means
idint (PK)Auto-incrementing ID.
namevarchar(150)Unique group name (e.g., "Editors").

auth_permission

ColumnTypeWhat it means
idint (PK)Auto-incrementing ID.
namevarchar(255)Human-readable name (e.g., "Can add post").
content_type_idint (FK)Which model this permission applies to.
codenamevarchar(100)Code identifier (e.g., add_post).

django_content_type

Maps every model in your project to an ID. The permission system uses it to know which model a permission applies to.

How permissions work

A permission is tied to a content type (a model). Groups collect permissions. Users are added to groups to inherit their permissions. You can also assign permissions directly to users.

Which migration creates which tables

The tables arrive in layers as you migrate. The contenttypes app migration creates django_content_type first, every model in your project gets one row. Then the auth app's initial migration creates auth_user, auth_group, auth_permission, and the three join tables. The admin app adds django_admin_log last. If you only need users without groups, you still get all of them, Django does not split the auth migrations.

The contenttypes trick

django_content_type is the clever bit: it maps (app_label, model) pairs to integer IDs, so permissions attach to models rather than tables. A permission row says "can change X" where X is a content type ID; the same mechanism powers generic foreign keys elsewhere in Django. This is why adding a new model and running migrations silently creates four new permission rows, one per default action.

A permission check in one query

To list everything user 42 can do, join through both paths, group memberships and direct grants:

SELECT DISTINCT p.codename
FROM auth_permission p
JOIN django_content_type ct ON ct.id = p.content_type_id
LEFT JOIN auth_group_permissions gp ON gp.permission_id = p.id
LEFT JOIN auth_user_groups ug ON ug.group_id = gp.group_id AND ug.user_id = 42
LEFT JOIN auth_user_user_permissions up ON up.permission_id = p.id AND up.user_id = 42
WHERE ug.user_id = 42 OR up.user_id = 42;

Note the two join tables doing different jobs: auth_user_groups carries role-like membership, while auth_user_user_permissions carries one-off grants. Most RBAC designs, see the RBAC schema, separate these the same way.

FAQ

Does Django create all these tables automatically?
Yes. Running python manage.py migrate creates all auth tables, contenttypes first, then auth, then admin.

How is this different from NextAuth or Laravel auth?
Django's permissions attach to models via content types, which neither NextAuth nor Laravel do. Compare the NextAuth schema (OAuth accounts and sessions, no permissions) and the Laravel schema (sessions and jobs, no permission tables at all).

What's the difference between is_superuser and is_staff?
is_superuser bypasses all permission checks. is_staff only controls access to the Django admin.

Can I use Django without the permission system?
Yes. Remove django.contrib.auth from INSTALLED_APPS.

Visualize your own database

Paste your PostgreSQL connection string and get an interactive ER diagram in under 10 seconds. No signup required.

Related guides

Try it on your database